หากต้องการให้แอปมีขนาดเล็กและรวดเร็วที่สุด คุณควรเพิ่มประสิทธิภาพและลดบิลด์ของรุ่นด้วย isMinifyEnabled = true
ซึ่งจะเปิดใช้การลดขนาด ซึ่งจะนำโค้ดที่ไม่ได้ใช้งานออก การปรับให้ยากต่อการอ่าน ซึ่งจะย่อชื่อคลาสและสมาชิกของแอป และการเพิ่มประสิทธิภาพ ซึ่งจะใช้กลยุทธ์การเพิ่มประสิทธิภาพโค้ดที่ปรับปรุงแล้วเพื่อลดขนาดและปรับปรุงประสิทธิภาพของแอปเพิ่มเติม หน้านี้อธิบายวิธีที่ R8 ทำงานเหล่านี้เมื่อคอมไพล์โปรเจ็กต์ของคุณและวิธีปรับแต่ง
เมื่อคุณสร้างโปรเจ็กต์โดยใช้ Android Gradle Plugin 3.4.0 ขึ้นไป ปลั๊กอินจะไม่ใช้ ProGuard เพื่อเพิ่มประสิทธิภาพโค้ดขณะคอมไพล์อีกต่อไป แต่จะใช้ร่วมกับคอมไพเลอร์ R8 เพื่อจัดการงานต่อไปนี้ในรันไทม์
- การย่อโค้ด (หรือ Tree-shaking): ตรวจหาและนําคลาส ฟิลด์ เมธอด และแอตทริบิวต์ที่ไม่ได้ใช้ออกจากแอปและไลบรารีที่เกี่ยวข้องอย่างปลอดภัย (จึงเป็นเครื่องมือที่มีประโยชน์ในการแก้ปัญหาขีดจํากัดการอ้างอิง 64,000) เช่น หากคุณใช้ API ของทรัพยากร Dependency ของไลบรารีเพียงไม่กี่รายการ การลดขนาดจะช่วยระบุโค้ดไลบรารีที่แอปของคุณไม่ได้ใช้อยู่และนำเฉพาะโค้ดนั้นออกจากแอป หากต้องการเรียนรู้เพิ่มเติม ให้ไปที่ส่วนวิธีย่อโค้ด
- การลดขนาดทรัพยากร: นำทรัพยากรที่ไม่ได้ใช้ออกจากแอปที่แพ็ก รวมถึงทรัพยากรที่ไม่ได้ใช้ใน Dependency ของไลบรารีของแอป ซึ่งทำงานร่วมกับการลดขนาดโค้ด เช่น เมื่อนำโค้ดที่ไม่ได้ใช้งานออกแล้ว ระบบจะนำทรัพยากรที่ไม่ได้อ้างอิงอีกต่อไปออกอย่างปลอดภัยด้วย ดูข้อมูลเพิ่มเติมได้ที่ส่วนวิธีลดขนาดทรัพยากร
- การเพิ่มประสิทธิภาพ: ตรวจสอบและเขียนโค้ดใหม่เพื่อปรับปรุงประสิทธิภาพรันไทม์และลดขนาดไฟล์ DEX ของแอปให้น้อยลง ซึ่งจะปรับปรุงประสิทธิภาพรันไทม์ของโค้ดได้สูงสุด 30% ซึ่งจะปรับปรุงเวลาเริ่มต้นและเฟรมได้อย่างมาก ตัวอย่างเช่น หาก R8 ตรวจพบว่าไม่มีการใช้ Branch ของ
else {}
สําหรับคําสั่ง if/else ที่ระบุ R8 จะนำโค้ดของ Branch ของelse {}
ออก ดูข้อมูลเพิ่มเติมได้ที่ส่วนการเพิ่มประสิทธิภาพโค้ด - การสร้างความสับสน (หรือการปรับขนาดตัวระบุให้เล็กลง): ย่อชื่อคลาสและสมาชิก ซึ่งทำให้ไฟล์ DEX มีขนาดลดลง ดูข้อมูลเพิ่มเติมได้ที่ส่วนเกี่ยวกับวิธีสร้างความสับสนให้กับโค้ด
เมื่อสร้างแอปเวอร์ชันที่เผยแพร่ คุณสามารถกําหนดค่า R8 ให้ทํางานต่างๆ ในเวลาคอมไพล์ตามที่อธิบายไว้ข้างต้นให้คุณได้ นอกจากนี้ คุณยังปิดใช้งานบางอย่างหรือปรับแต่งลักษณะการทํางานของ R8 ผ่านไฟล์กฎ ProGuard ได้ด้วย อันที่จริงแล้ว R8 ใช้งานได้กับไฟล์กฎ ProGuard ที่มีอยู่ทั้งหมด ดังนั้นการอัปเดตปลั๊กอิน Android Gradle เพื่อใช้ R8 จึงไม่จําเป็นต้องเปลี่ยนกฎที่มีอยู่
เปิดใช้การย่อ การสร้างความสับสน และการเพิ่มประสิทธิภาพ
เมื่อใช้ Android Studio 3.4 หรือปลั๊กอิน Android Gradle 3.4.0 ขึ้นไป R8 จะเป็นคอมไพเลอร์เริ่มต้นที่จะแปลงไบต์โค้ด Java ของโปรเจ็กต์เป็นรูปแบบ DEX ที่ทำงานบนแพลตฟอร์ม Android อย่างไรก็ตาม เมื่อคุณสร้างโปรเจ็กต์ใหม่โดยใช้ Android Studio ระบบจะไม่เปิดใช้การย่อขนาด การสร้างความสับสน และการเพิ่มประสิทธิภาพโค้ดโดยค่าเริ่มต้น เนื่องจากการเพิ่มประสิทธิภาพเวลาคอมไพล์เหล่านี้จะเพิ่มเวลาสร้างโปรเจ็กต์และอาจทำให้เกิดข้อบกพร่องหากคุณปรับแต่งโค้ดที่จะเก็บไว้ไม่เพียงพอ
ดังนั้นคุณจึงควรเปิดใช้งานงานเวลาคอมไพล์เหล่านี้เมื่อสร้างแอปเวอร์ชันสุดท้ายที่จะทดสอบก่อนที่จะเผยแพร่ หากต้องการเปิดใช้การย่อ การสร้างความสับสน และการเพิ่มประสิทธิภาพ ให้ใส่ข้อมูลต่อไปนี้ในสคริปต์บิลด์ระดับโปรเจ็กต์
android { buildTypes { getByName("release") { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `isDebuggable=false`. isMinifyEnabled = true // Enables resource shrinking, which is performed by the // Android Gradle plugin. isShrinkResources = true proguardFiles( // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. getDefaultProguardFile("proguard-android-optimize.txt"), // Includes a local, custom Proguard rules file "proguard-rules.pro" ) } } ... }
android { buildTypes { release { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `debuggable false`. minifyEnabled true // Enables resource shrinking, which is performed by the // Android Gradle plugin. shrinkResources true // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt'), 'proguard-rules.pro' } } ... }
ไฟล์การกําหนดค่า R8
R8 ใช้ไฟล์กฎ ProGuard เพื่อแก้ไขลักษณะการทำงานเริ่มต้นและทําความเข้าใจโครงสร้างของแอปได้ดียิ่งขึ้น เช่น คลาสที่ทำหน้าที่เป็นจุดแรกเข้าของโค้ดแอป แม้ว่าคุณจะแก้ไขไฟล์กฎบางไฟล์ได้ แต่เครื่องมือเวลาคอมไพล์ เช่น AAPT2 อาจสร้างกฎบางรายการโดยอัตโนมัติ หรืออาจรับค่ามาจากไลบรารีที่แอปของคุณใช้ร่วมกัน ตารางด้านล่างอธิบายแหล่งที่มาของไฟล์กฎ ProGuard ที่ R8 ใช้
แหล่งที่มา | ตำแหน่ง | คำอธิบาย |
Android Studio | <module-dir>/proguard-rules.pro
|
เมื่อคุณสร้างโมดูลใหม่โดยใช้ Android Studio แล้ว IDE จะสร้างไฟล์ proguard-rules.pro ในไดเรกทอรีรากของโมดูลนั้น
โดยค่าเริ่มต้น ไฟล์นี้จะไม่มีการใช้กฎใดๆ ดังนั้น ให้ใส่กฎ ProGuard ของคุณเองที่นี่ เช่น กฎการเก็บรักษาที่กําหนดเอง |
ปลั๊กอิน Android Gradle | สร้างโดยปลั๊กอิน Android Gradle ในเวลาคอมไพล์ | ปลั๊กอิน Android Gradle จะสร้าง proguard-android-optimize.txt ซึ่งมีกฎที่เป็นประโยชน์ต่อโปรเจ็กต์ Android ส่วนใหญ่ และเปิดใช้@Keep* คำอธิบายประกอบ
โดยค่าเริ่มต้น เมื่อสร้างโมดูลใหม่โดยใช้ Android Studio สคริปต์การสร้างระดับโมดูลจะรวมไฟล์กฎนี้ไว้ในบิลด์รุ่นให้คุณ
หมายเหตุ: ปลั๊กอิน Android Gradle มีไฟล์กฎ ProGuard ที่กำหนดไว้ล่วงหน้าเพิ่มเติม แต่เราขอแนะนำให้ใช้ |
ทรัพยากร Dependency ของไลบรารี |
ในคลัง AAR
ในไลบรารี JAR นอกจากตำแหน่งเหล่านี้แล้ว ปลั๊กอิน Android Gradle 3.6 ขึ้นไปยังรองรับกฎการบีบอัดแบบกำหนดเป้าหมายด้วย |
หากไลบรารี AAR หรือ JAR เผยแพร่พร้อมไฟล์กฎของตนเอง และคุณรวมไลบรารีนั้นเป็นทรัพยากร Dependency ของเวลาคอมไพล์ R8 จะใช้กฎเหล่านั้นโดยอัตโนมัติเมื่อคอมไพล์โปรเจ็กต์ นอกจากกฎ ProGuard แบบดั้งเดิมแล้ว ปลั๊กอิน Android Gradle เวอร์ชัน 3.6 ขึ้นไปยังรองรับกฎการบีบอัดแบบกำหนดเป้าหมายด้วย กฎเหล่านี้กำหนดเป้าหมายไปยังเครื่องมือบีบอัดที่เฉพาะเจาะจง (R8 หรือ ProGuard) รวมถึงเวอร์ชันเครื่องมือบีบอัดที่เฉพาะเจาะจง การใช้ไฟล์กฎที่รวมอยู่ในไลบรารีจะมีประโยชน์หากต้องใช้กฎบางอย่างเพื่อให้ไลบรารีทํางานได้อย่างถูกต้อง กล่าวคือ นักพัฒนาไลบรารีได้ดําเนินการตามขั้นตอนการแก้ปัญหาให้คุณแล้ว อย่างไรก็ตาม โปรดทราบว่าเนื่องจากกฎเป็นการเพิ่ม คุณจึงนำกฎบางอย่างที่ไลบรารี Dependency รวมไว้ออกไม่ได้ และอาจส่งผลต่อการคอมไพล์ส่วนอื่นๆ ของแอป เช่น หากไลบรารีมีกฎในการปิดใช้การเพิ่มประสิทธิภาพโค้ด กฎนั้นจะปิดใช้การเพิ่มประสิทธิภาพสำหรับทั้งโปรเจ็กต์ |
เครื่องมือแพ็กเกจเนื้อหา Android 2 (AAPT2) | หลังจากสร้างโปรเจ็กต์ด้วย minifyEnabled true แล้ว ให้ทำดังนี้
<module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt
|
AAPT2 จะสร้างกฎการเก็บรักษาตามการอ้างอิงคลาสในไฟล์ Manifest, เลย์เอาต์ และทรัพยากรอื่นๆ ของแอป ตัวอย่างเช่น AAPT2 มีกฎการเก็บไว้สําหรับกิจกรรมแต่ละรายการที่คุณลงทะเบียนในไฟล์ Manifest ของแอปเป็นจุดแรกเข้า |
ไฟล์การกําหนดค่าที่กําหนดเอง | โดยค่าเริ่มต้น เมื่อคุณสร้างโมดูลใหม่โดยใช้ Android Studio IDE จะสร้าง <module-dir>/proguard-rules.pro ให้คุณเพื่อเพิ่มกฎของคุณเอง
|
คุณสามารถรวมการกําหนดค่าเพิ่มเติมได้ และ R8 จะใช้การกําหนดค่าเหล่านั้นเมื่อคอมไพล์ |
เมื่อคุณตั้งค่าพร็อพเพอร์ตี้ minifyEnabled
เป็น true
แล้ว R8 จะรวมกฎจากแหล่งที่มาทั้งหมดที่พร้อมใช้งานซึ่งระบุไว้ข้างต้น โปรดคำนึงถึงเรื่องนี้เมื่อแก้ปัญหาเกี่ยวกับ R8 เนื่องจากไลบรารีอื่นๆ ที่ใช้ร่วมกันในรันไทม์ เช่น ไลบรารีที่ใช้ร่วมกัน อาจทำให้เกิดการเปลี่ยนแปลงลักษณะการทำงานของ R8 ที่คุณไม่ทราบ
หากต้องการแสดงรายงานฉบับเต็มของกฎทั้งหมดที่ R8 นำไปใช้เมื่อสร้างโปรเจ็กต์ ให้ใส่ข้อมูลต่อไปนี้ในไฟล์ proguard-rules.pro
ของโมดูล
// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt
กฎการลดขนาดที่กำหนดเป้าหมาย
ปลั๊กอิน Gradle ของ Android เวอร์ชัน 3.6 ขึ้นไปรองรับกฎของไลบรารีที่กำหนดเป้าหมายไปยังเครื่องมือบีบอัดที่เฉพาะเจาะจง (R8 หรือ ProGuard) รวมถึงเวอร์ชันเครื่องมือบีบอัดที่เฉพาะเจาะจง วิธีนี้ช่วยให้นักพัฒนาไลบรารีปรับแต่งกฎให้ทำงานได้อย่างมีประสิทธิภาพสูงสุดในโปรเจ็กต์ที่ใช้โปรแกรมบีบอัดเวอร์ชันใหม่ ขณะเดียวกันก็อนุญาตให้ใช้กฎที่มีอยู่ต่อไปในโปรเจ็กต์ที่ใช้โปรแกรมบีบอัดเวอร์ชันเก่า
หากต้องการระบุกฎการบีบอัดที่กําหนดเป้าหมาย นักพัฒนาคลังจะต้องใส่กฎเหล่านั้นไว้ที่ตําแหน่งเฉพาะภายในคลัง AAR หรือ JAR ตามที่อธิบายไว้ด้านล่าง
In an AAR library:
proguard.txt (legacy location)
classes.jar
└── META-INF
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
In a JAR library:
META-INF
├── proguard/<ProGuard-rules-file> (legacy location)
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
ซึ่งหมายความว่ากฎการย่อขนาดเป้าหมายจะเก็บอยู่ในไดเรกทอรี META-INF/com.android.tools
ของ JAR หรือในไดเรกทอรี META-INF/com.android.tools
ภายใน classes.jar
ของ AAR
ในไดเรกทอรีดังกล่าว อาจมีหลายไดเรกทอรีที่มีชื่อในรูปแบบ r8-from-<X>-upto-<Y>
หรือ proguard-from-<X>-upto-<Y>
เพื่อระบุว่าเวอร์ชันใดที่ลดขนาดกฎภายในไดเรกทอรีที่เขียนขึ้นมา
โปรดทราบว่าส่วน -from-<X>
และ -upto-<Y>
เป็นส่วนที่ไม่บังคับ เวอร์ชัน <Y>
เป็นแบบเฉพาะตัว และช่วงเวอร์ชันต้องต่อเนื่องกัน
เช่น r8-upto-8.0.0
, r8-from-8.0.0-upto-8.2.0
และ r8-from-8.2.0
จะสร้างชุดกฎการย่อเป้าหมายที่ถูกต้อง กฎภายใต้ไดเรกทอรี r8-from-8.0.0-upto-8.2.0
จะใช้โดย R8 ตั้งแต่เวอร์ชัน 8.0.0 ขึ้นไป แต่ไม่รวมเวอร์ชัน 8.2.0
เมื่อทราบข้อมูลดังกล่าวแล้ว ปลั๊กอิน Android Gradle 3.6 ขึ้นไปจะเลือกกฎจากไดเรกทอรี R8 ที่ตรงกัน หากไลบรารีไม่ได้ระบุกฎการบีบอัดเป้าหมาย ปลั๊กอิน Gradle ของ Android จะเลือกกฎจากตำแหน่งเดิม (proguard.txt
สำหรับ AAR หรือ META-INF/proguard/<ProGuard-rules-file>
สำหรับ JAR)
นักพัฒนาไลบรารีสามารถเลือกที่จะรวมกฎการบีบอัดแบบกำหนดเป้าหมายหรือกฎ ProGuard รุ่นเดิมไว้ในไลบรารี หรือจะรวมทั้ง 2 ประเภทก็ได้หากต้องการคงความเข้ากันได้กับปลั๊กอิน Gradle ของ Android ที่เก่ากว่า 3.6 หรือเครื่องมืออื่นๆ
รวมการกําหนดค่าเพิ่มเติม
เมื่อคุณสร้างโปรเจ็กต์หรือโมดูลใหม่โดยใช้ Android Studio IDE จะสร้างไฟล์ <module-dir>/proguard-rules.pro
ให้คุณเพื่อใส่กฎของคุณเอง นอกจากนี้ คุณยังรวมกฎเพิ่มเติมจากไฟล์อื่นๆ ได้โดยเพิ่มลงในพร็อพเพอร์ตี้ proguardFiles
ในสคริปต์บิลด์ของโมดูล
เช่น คุณสามารถเพิ่มกฎเฉพาะสำหรับตัวแปรการสร้างแต่ละรายการได้โดยเพิ่มพร็อพเพอร์ตี้ proguardFiles
อื่นในบล็อก productFlavor
ที่เกี่ยวข้อง ไฟล์ Gradle ต่อไปนี้เพิ่ม flavor2-rules.pro
ลงในเวอร์ชันผลิตภัณฑ์ flavor2
ตอนนี้ flavor2
ใช้กฎ ProGuard ทั้ง 3 ข้อ เนื่องจากมีการใช้กฎจากบล็อก release
ด้วย
นอกจากนี้ คุณยังเพิ่มพร็อพเพอร์ตี้ testProguardFiles
ซึ่งระบุรายการไฟล์ ProGuard ที่รวมอยู่ใน APK ทดสอบเท่านั้นได้ ดังนี้
android { ... buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). "proguard-rules.pro" ) testProguardFiles( // The proguard files listed here are included in the // test APK only. "test-proguard-rules.pro" ) } } flavorDimensions.add("version") productFlavors { create("flavor1") { ... } create("flavor2") { proguardFile("flavor2-rules.pro") } } }
android { ... buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). 'proguard-rules.pro' testProguardFiles // The proguard files listed here are included in the // test APK only. 'test-proguard-rules.pro' } } flavorDimensions "version" productFlavors { flavor1 { ... } flavor2 { proguardFile 'flavor2-rules.pro' } } }
ย่อขนาดโค้ด
ระบบจะเปิดใช้การลดขนาดโค้ดด้วย R8 โดยค่าเริ่มต้นเมื่อคุณตั้งค่าพร็อพเพอร์ตี้ minifyEnabled
เป็น true
การลดขนาดโค้ด (เรียกอีกอย่างว่า Tree Shaking) คือกระบวนการนําโค้ดที่ R8 พิจารณาว่าไม่จําเป็นในรันไทม์ออก ขั้นตอนนี้สามารถลดขนาดของแอปได้อย่างมาก เช่น หากแอปมีทรัพยากร Dependency ของไลบรารีจำนวนมากแต่ใช้ประโยชน์จากฟังก์ชันการทํางานเพียงส่วนเล็กๆ
หากต้องการย่อโค้ดของแอป R8 จะกำหนดจุดแรกเข้าทั้งหมดในโค้ดของแอปตามชุดไฟล์การกำหนดค่าแบบรวมก่อน จุดเข้าเหล่านี้ประกอบด้วยคลาสทั้งหมดที่แพลตฟอร์ม Android อาจใช้เพื่อเปิดกิจกรรมหรือบริการของแอป โดย R8 จะตรวจสอบโค้ดของแอปจากจุดแรกเข้าแต่ละจุดเพื่อสร้างกราฟของเมธอด ตัวแปรสมาชิก และคลาสอื่นๆ ทั้งหมดที่แอปอาจเข้าถึงขณะรันไทม์ ระบบจะถือว่าโค้ดที่ไม่ได้เชื่อมต่อกับกราฟนั้นเข้าถึงไม่ได้และอาจนําออกจากแอป
รูปที่ 1 แสดงแอปที่มีทรัพยากร Dependency ของไลบรารีรันไทม์ ขณะตรวจสอบโค้ดของแอป R8 จะระบุว่าเข้าถึงเมธอด foo()
, faz()
และ bar()
ได้จากจุดแรกเข้า MainActivity.class
อย่างไรก็ตาม แอปของคุณจะไม่ใช้คลาส OkayApi.class
หรือเมธอด baz()
ในระหว่างรันไทม์ และ R8 จะนำโค้ดดังกล่าวออกเมื่อลดขนาดแอป
รูปที่ 1 ขณะคอมไพล์ R8 จะสร้างกราฟตามกฎการเก็บรักษาแบบรวมของโปรเจ็กต์เพื่อระบุโค้ดที่เข้าถึงไม่ได้
R8 จะกำหนดจุดแรกเข้าผ่านกฎ -keep
ในไฟล์การกำหนดค่า R8 ของโปรเจ็กต์ กล่าวคือ กฎการเก็บรักษาจะระบุคลาสที่ R8 ไม่ควรทิ้งเมื่อลดขนาดแอป และ R8 จะถือว่าคลาสเหล่านั้นเป็นจุดเข้าใช้งานที่เป็นไปได้ของแอป ปลั๊กอิน Android Gradle และ AAPT2 จะสร้างกฎการเก็บรักษาที่จําเป็นสําหรับโปรเจ็กต์แอปส่วนใหญ่ให้คุณโดยอัตโนมัติ เช่น กิจกรรม มุมมอง และบริการของแอป อย่างไรก็ตาม หากต้องการปรับแต่งลักษณะการทำงานเริ่มต้นนี้ด้วยกฎ Keep เพิ่มเติม โปรดอ่านส่วนเกี่ยวกับวิธีปรับแต่งโค้ดที่จะเก็บไว้
แต่หากสนใจเฉพาะการลดขนาดทรัพยากรของแอป ให้ข้ามไปที่ส่วนเกี่ยวกับวิธีลดขนาดทรัพยากร
โปรดทราบว่าหากมีการลดขนาดโปรเจ็กต์ไลบรารี แอปที่ขึ้นกับไลบรารีนั้นจะมีคลาสไลบรารีที่ลดขนาด คุณอาจต้องปรับกฎการเก็บรักษาคลังหากมีคลาสที่หายไปใน APK ของคลัง หากคุณกำลังสร้างและเผยแพร่ไลบรารีในรูปแบบ AAR ไฟล์ JAR ในเครื่องที่ไลบรารีของคุณใช้จะไม่ถูกบีบอัดในไฟล์ AAR
ปรับแต่งรหัสที่จะเก็บไว้
ในกรณีส่วนใหญ่ ไฟล์กฎ ProGuard เริ่มต้น (proguard-android-optimize.txt
) ก็เพียงพอแล้วสำหรับ R8 ในการนําเฉพาะโค้ดที่ไม่ได้ใช้ออก อย่างไรก็ตาม บางสถานการณ์อาจทำให้ R8 วิเคราะห์อย่างถูกต้องได้ยากและอาจนำโค้ดที่แอปต้องใช้ออก ตัวอย่างกรณีที่อาจนำโค้ดออกอย่างไม่ถูกต้อง ได้แก่
- เมื่อแอปเรียกใช้เมธอดจาก Java Native Interface (JNI)
- เมื่อแอปค้นหาโค้ดขณะรันไทม์ (เช่น ขณะมีเงาสะท้อน)
การทดสอบแอปควรแสดงข้อผิดพลาดที่เกิดจากโค้ดที่ถูกนำออกอย่างไม่เหมาะสม แต่คุณยังตรวจสอบโค้ดที่ถูกนำออกได้ด้วยการสร้างรายงานโค้ดที่ถูกนำออก
หากต้องการแก้ไขข้อผิดพลาดและบังคับให้ R8 เก็บโค้ดบางรายการไว้ ให้เพิ่มบรรทัด -keep
ในไฟล์กฎ ProGuard เช่น
-keep public class MyClass
หรือจะเพิ่มคำอธิบายประกอบ @Keep
ลงในโค้ดที่ต้องการเก็บไว้ก็ได้ การเพิ่ม @Keep
ในคลาสจะทำให้คลาสทั้งคลาสเหมือนเดิม ส่วนการเพิ่มในเมธอดหรือฟิลด์จะทำให้เมธอด/ฟิลด์ (และชื่อ) รวมถึงชื่อคลาสเหมือนเดิม โปรดทราบว่าคำอธิบายประกอบนี้พร้อมใช้งานเฉพาะเมื่อใช้ไลบรารีคำอธิบายประกอบของ AndroidX และเมื่อคุณรวมไฟล์กฎ ProGuard ที่มากับปลั๊กอิน Android Gradle ตามที่อธิบายไว้ในส่วนเกี่ยวกับวิธีเปิดใช้การย่อ
คุณควรพิจารณาหลายอย่างเมื่อใช้ตัวเลือก -keep
ดูข้อมูลเพิ่มเติมเกี่ยวกับการปรับแต่งไฟล์กฎได้ที่คู่มือ ProGuard
ส่วนการแก้ปัญหาจะระบุปัญหาทั่วไปอื่นๆ ที่คุณอาจพบเมื่อระบบนำโค้ดออก
ลบไลบรารีที่มาพร้อมเครื่อง
โดยค่าเริ่มต้น ไลบรารีโค้ดแบบเนทีฟจะถูกตัดออกในเวอร์ชันการเผยแพร่ของแอป การตัดนี้ประกอบด้วยการนำตารางสัญลักษณ์และข้อมูลการแก้ไขข้อบกพร่องที่มีอยู่ในไลบรารีเนทีฟที่แอปใช้อยู่ การถอดไลบรารีโค้ดแบบเนทีฟช่วยลดขนาดได้อย่างมาก อย่างไรก็ตาม คุณไม่สามารถวินิจฉัยข้อขัดข้องใน Google Play Console ได้เนื่องจากข้อมูลขาดหายไป (เช่น ชื่อคลาสและฟังก์ชัน)
การสนับสนุนด้านการขัดข้องของระบบ
Google Play Console จะรายงานข้อขัดข้องที่เกิดในแอปเองในส่วน Android Vitals คุณสามารถสร้างและอัปโหลดไฟล์สัญลักษณ์การแก้ไขข้อบกพร่องของระบบสำหรับแอปได้ง่ายๆ ในไม่กี่ขั้นตอน ไฟล์นี้เปิดใช้สแต็กเทรซข้อขัดข้องของระบบที่แทนที่ด้วยสัญลักษณ์ (ซึ่งรวมถึงชื่อคลาสและฟังก์ชัน) ใน Android Vitals เพื่อช่วยแก้ไขข้อบกพร่องของแอปในเวอร์ชันที่ใช้งานจริง ขั้นตอนเหล่านี้จะแตกต่างกันไปตามเวอร์ชันของปลั๊กอิน Android Gradle ที่ใช้กับโปรเจ็กต์และเอาต์พุตการสร้างของโปรเจ็กต์
ปลั๊กอิน Android Gradle เวอร์ชัน 4.1 ขึ้นไป
หากเป็นโปรเจ็กต์สร้าง Android App Bundle คุณจะรวมไฟล์สัญลักษณ์การแก้ไขข้อบกพร่องในแอปที่มาพร้อมเครื่องไว้ในนั้นได้โดยอัตโนมัติ หากต้องการรวมไฟล์นี้ไว้ในบิลด์รุ่น ให้เพิ่มรายการต่อไปนี้ลงในไฟล์ build.gradle.kts
ของแอป
android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }
เลือกระดับสัญลักษณ์การแก้ไขข้อบกพร่องจากรายการต่อไปนี้
- ใช้
SYMBOL_TABLE
เพื่อดูชื่อฟังก์ชันในสแต็กเทรซที่แทนที่ด้วยสัญลักษณ์ของ Play Console ระดับนี้รองรับ Tombstone - ใช้
FULL
เพื่อดูชื่อฟังก์ชัน ไฟล์ และหมายเลขบรรทัดในสแต็กเทรซที่มีสัญลักษณ์ของ Play Console
หากเป็นโปรเจ็กต์สร้าง APK ให้ใช้การตั้งค่าบิลด์ build.gradle.kts
ที่แสดงไว้ก่อนหน้านี้เพื่อสร้างไฟล์สัญลักษณ์การแก้ไขข้อบกพร่องของระบบแยกกัน อัปโหลดไฟล์สัญลักษณ์การแก้ไขข้อบกพร่องของระบบไปยัง Google Play Console ด้วยตนเอง เนื่องจากเป็นส่วนหนึ่งของกระบวนการบิลด์ ปลั๊กอิน Android Gradle จะเอาต์พุตไฟล์นี้ในตำแหน่งของโปรเจ็กต์ดังต่อไปนี้
app/build/outputs/native-debug-symbols/variant-name/native-debug-symbols.zip
ปลั๊กอิน Android Gradle เวอร์ชัน 4.0 ลงไป (และระบบบิลด์อื่นๆ)
เนื่องจากเป็นส่วนหนึ่งของกระบวนการบิลด์ ปลั๊กอิน Android Gradle จะเก็บสำเนาของไลบรารีที่ยังไม่ได้ Strip เอาไว้ในไดเรกทอรีของโปรเจ็กต์ โครงสร้างไดเรกทอรีนี้คล้ายกับตัวอย่างต่อไปนี้
app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── arm64-v8a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── x86/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
└── x86_64/
├── libgameengine.so
├── libothercode.so
└── libvideocodec.so
บีบอัดเนื้อหาของไดเรกทอรีนี้
cd app/build/intermediates/cmake/universal/release/obj
zip -r symbols.zip .
อัปโหลดไฟล์
symbols.zip
ไปยัง Google Play Console ด้วยตนเอง
ย่อขนาดทรัพยากร
การลดขนาดทรัพยากรใช้ได้กับการทำงานร่วมกับการลดขนาดโค้ดเท่านั้น หลังจากที่เครื่องมือลดขนาดโค้ดนำโค้ดที่ไม่ได้ใช้งานทั้งหมดออกแล้ว เครื่องมือลดขนาดทรัพยากรจะระบุทรัพยากรที่แอปยังคงใช้อยู่ได้ กรณีนี้เกิดขึ้นเมื่อคุณเพิ่มไลบรารีโค้ดที่มีทรัพยากร โดยคุณต้องนำโค้ดไลบรารีที่ไม่ได้ใช้ออกเพื่อให้ระบบยกเลิกการอ้างอิงทรัพยากรของไลบรารี ซึ่งจะทำให้เครื่องมือบีบอัดทรัพยากรนำทรัพยากรดังกล่าวออกได้
หากต้องการเปิดใช้การลดขนาดทรัพยากร ให้ตั้งค่าพร็อพเพอร์ตี้ shrinkResources
เป็น true
ในสคริปต์บิลด์ (ควบคู่ไปกับ minifyEnabled
สำหรับการลดขนาดโค้ด) เช่น
android { ... buildTypes { getByName("release") { isShrinkResources = true isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } }
android { ... buildTypes { release { shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
หากยังไม่ได้สร้างแอปโดยใช้ minifyEnabled
สำหรับการลดขนาดโค้ด ให้ลองใช้ก่อนเปิดใช้ shrinkResources
เนื่องจากคุณอาจต้องแก้ไขไฟล์ proguard-rules.pro
เพื่อเก็บคลาสหรือเมธอดที่สร้างหรือเรียกใช้แบบไดนามิกไว้ก่อนเริ่มนำทรัพยากรออก
ปรับแต่งทรัพยากรที่จะเก็บไว้
หากมีทรัพยากรที่เฉพาะเจาะจงที่ต้องการเก็บหรือทิ้ง ให้สร้างไฟล์ XML ในโปรเจ็กต์โดยใช้แท็ก <resources>
แล้วระบุทรัพยากรแต่ละรายการที่จะเก็บไว้ในแอตทริบิวต์ tools:keep
และทรัพยากรแต่ละรายการที่จะทิ้งไว้ในแอตทริบิวต์ tools:discard
ทั้ง 2 แอตทริบิวต์ยอมรับรายการชื่อทรัพยากรที่คั่นด้วยคอมมา คุณใช้เครื่องหมายดอกจันเป็นไวลด์การ์ดได้
เช่น
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*" tools:discard="@layout/unused2" />
บันทึกไฟล์นี้ในทรัพยากรของโปรเจ็กต์ เช่น ที่ res/raw/my.package.keep.xml
บิลด์ไม่ได้บรรจุไฟล์นี้ลงในแอป
หมายเหตุ: ตรวจสอบว่าคุณใช้ชื่อที่ไม่ซ้ำกันสำหรับไฟล์ keep
เมื่อมีการลิงก์คลังต่างๆ เข้าด้วยกัน กฎการเก็บรักษาของคลังจะขัดแย้งกัน ซึ่งอาจทำให้เกิดปัญหาเกี่ยวกับกฎที่ถูกละเว้นหรือทรัพยากรที่เก็บไว้โดยไม่จำเป็น
การระบุทรัพยากรที่จะทิ้งอาจดูไร้ประโยชน์เมื่อคุณควรลบทรัพยากรเหล่านั้นแทน แต่วิธีนี้จะมีประโยชน์เมื่อใช้ตัวแปรบิลด์ ตัวอย่างเช่น คุณอาจใส่ทรัพยากรทั้งหมดไว้ในไดเรกทอรีโปรเจ็กต์ทั่วไป จากนั้นสร้างไฟล์ my.package.build.variant.keep.xml
ที่แตกต่างกันสำหรับตัวแปรการสร้างแต่ละรายการเมื่อทราบว่ามีการใช้ทรัพยากรหนึ่งๆ ในโค้ด (และเครื่องมือบีบอัดจึงไม่นำออก) แต่คุณทราบว่าจะไม่ได้ใช้ทรัพยากรนั้นกับตัวแปรการสร้างหนึ่งๆ นอกจากนี้อาจเป็นไปได้ว่าเครื่องมือสร้างระบุทรัพยากรไม่ถูกต้องตามความจำเป็น ซึ่งเป็นไปได้เนื่องจากคอมไพเลอร์เพิ่มรหัสทรัพยากรในบรรทัด จากนั้นเครื่องมือวิเคราะห์ทรัพยากรอาจไม่ทราบถึงความแตกต่างระหว่างทรัพยากรที่อ้างอิงจริงกับค่าจำนวนเต็มในโค้ดที่เกิดขึ้นมีค่าเหมือนกัน
เปิดใช้การตรวจสอบการอ้างอิงอย่างเข้มงวด
โดยปกติแล้ว ตัวลดขนาดทรัพยากรจะระบุได้อย่างแม่นยำว่าทรัพยากรมีการใช้งานหรือไม่ อย่างไรก็ตาม หากโค้ดของคุณเรียกใช้
Resources.getIdentifier()
(หรือหากไลบรารีของคุณทำแบบนั้น ไลบรารี AppCompat ก็มี) แสดงว่าโค้ดจะค้นหาชื่อทรัพยากรตามสตริงที่สร้างขึ้นแบบไดนามิก เมื่อดำเนินการดังกล่าว ตัวลดขนาดทรัพยากรจะทำงานแบบป้องกันโดยค่าเริ่มต้น และทําเครื่องหมายทรัพยากรทั้งหมดที่มีรูปแบบชื่อตรงกันว่ามีการใช้งานและนำออกไม่ได้
ตัวอย่างเช่น โค้ดต่อไปนี้จะทำให้ระบบทำเครื่องหมายทรัพยากรทั้งหมดที่มีคำนำหน้า img_
ว่าใช้แล้ว
val name = String.format("img_%1d", angle + 1) val res = resources.getIdentifier(name, "drawable", packageName)
String name = String.format("img_%1d", angle + 1); res = getResources().getIdentifier(name, "drawable", getPackageName());
นอกจากนี้ ตัวลดขนาดทรัพยากรจะตรวจสอบสตริงคงที่ทั้งหมดในโค้ด รวมถึงทรัพยากร res/raw/
ต่างๆ เพื่อหา URL ของทรัพยากรในรูปแบบที่คล้ายกับ file:///android_res/drawable//ic_plus_anim_016.png
หากพบสตริงเช่นนี้หรือสตริงอื่นๆ ที่ดูเหมือนว่าอาจใช้สร้าง URL เช่นนี้ ระบบจะไม่นำสตริงเหล่านั้นออก
ตัวอย่างโหมดการลดขนาดอย่างปลอดภัยที่เปิดใช้โดยค่าเริ่มต้นมีดังนี้
อย่างไรก็ตาม คุณสามารถปิดการจัดการที่ "ปลอดภัยดีกว่าขอโทษ" นี้ แล้วระบุว่าเครื่องมือย่อทรัพยากรเก็บเฉพาะทรัพยากรที่จำเป็นต้องใช้เท่านั้น โดยให้ตั้งค่า shrinkMode
เป็น strict
ในไฟล์ keep.xml
ดังนี้
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="strict" />
หากเปิดใช้โหมดการย่อขนาดที่เข้มงวดและโค้ดของคุณยังอ้างอิงทรัพยากรที่มีสตริงที่สร้างขึ้นแบบไดนามิกดังที่แสดงด้านบน คุณจะต้องเก็บทรัพยากรเหล่านั้นด้วยตนเองโดยใช้แอตทริบิวต์ tools:keep
นำทรัพยากรทางเลือกที่ไม่ได้ใช้ออก
ตัวลดขนาดทรัพยากร Gradle จะนําเฉพาะทรัพยากรที่ไม่ได้อ้างอิงโดยโค้ดแอปออก ซึ่งหมายความว่าจะไม่นํา
ทรัพยากรทางเลือกสําหรับการกําหนดค่าอุปกรณ์ที่แตกต่างกันออก หากจำเป็น คุณสามารถใช้พร็อพเพอร์ตี้ resConfigs
ของปลั๊กอิน Android Gradle เพื่อนำไฟล์ทรัพยากรทางเลือกที่แอปไม่จำเป็นต้องใช้ออก
ตัวอย่างเช่น หากคุณใช้ไลบรารีที่มีทรัพยากรภาษา (เช่น AppCompat หรือ Google Play Services) แอปของคุณจะมีสตริงภาษาที่แปลแล้วทั้งหมดสำหรับข้อความในไลบรารีเหล่านั้น ไม่ว่าส่วนที่เหลือของแอปจะแปลเป็นภาษาเดียวกันหรือไม่ก็ตาม หากต้องการเก็บเฉพาะภาษาที่แอปรองรับอย่างเป็นทางการไว้ คุณสามารถระบุภาษาเหล่านั้นได้โดยใช้พร็อพเพอร์ตี้ resConfig
ระบบจะนำทรัพยากรสำหรับภาษาที่ไม่ได้ระบุออก
ข้อมูลโค้ดต่อไปนี้แสดงวิธีจำกัดทรัพยากรภาษาให้เหลือเพียงภาษาอังกฤษและฝรั่งเศส
android { defaultConfig { ... resourceConfigurations.addAll(listOf("en", "fr")) } }
android { defaultConfig { ... resConfigs "en", "fr" } }
เมื่อเผยแพร่แอปโดยใช้รูปแบบ App Bundle ของ Android ระบบจะดาวน์โหลดเฉพาะภาษาที่กําหนดค่าไว้ในอุปกรณ์ของผู้ใช้เมื่อติดตั้งแอป โดยค่าเริ่มต้น ในทํานองเดียวกัน ระบบจะรวมเฉพาะทรัพยากรที่ตรงกับความหนาแน่นของหน้าจอของอุปกรณ์ และไลบรารีแบบเนทีฟที่ตรงกับ ABI ของอุปกรณ์ไว้ในการดาวน์โหลด ดูข้อมูลเพิ่มเติมได้ที่การกำหนดค่า Android App Bundle
สำหรับแอปเดิมที่เผยแพร่ด้วย APK (สร้างขึ้นก่อนเดือนสิงหาคม 2021) คุณสามารถปรับแต่งความละเอียดของหน้าจอหรือทรัพยากร ABI ที่จะรวมไว้ใน APK ได้โดยสร้าง APK หลายรายการที่แต่ละรายการกำหนดเป้าหมายเป็นการกำหนดค่าอุปกรณ์ที่แตกต่างกัน
ผสานทรัพยากรที่ซ้ำกัน
โดยค่าเริ่มต้น Gradle จะรวมทรัพยากรที่มีชื่อเหมือนกันด้วย เช่น ทรัพยากรที่มีชื่อเดียวกันซึ่งอาจอยู่ในโฟลเดอร์ทรัพยากรต่างกัน ลักษณะการทํางานนี้ไม่ได้ควบคุมโดยพร็อพเพอร์ตี้ shrinkResources
และปิดใช้ไม่ได้ เนื่องจากจําเป็นต่อการป้องกันข้อผิดพลาดเมื่อแหล่งข้อมูลหลายรายการตรงกับชื่อที่โค้ดของคุณค้นหา
การรวมทรัพยากรจะเกิดขึ้นก็ต่อเมื่อไฟล์อย่างน้อย 2 ไฟล์มีชื่อ ประเภท และตัวระบุทรัพยากรเหมือนกันเท่านั้น Gradle จะเลือกไฟล์ที่พิจารณาว่าดีที่สุดจากไฟล์ที่ซ้ำกัน (ตามลําดับความสําคัญที่อธิบายไว้ด้านล่าง) และส่งทรัพยากรเพียงไฟล์เดียวนั้นไปยัง AAPT เพื่อเผยแพร่ในแอตทริบิวต์สุดท้าย
Gradle จะค้นหาทรัพยากรที่ซ้ำกันในส่วนต่อไปนี้
- ทรัพยากรหลักที่เชื่อมโยงกับชุดแหล่งที่มาหลัก โดยทั่วไปจะอยู่ใน
src/main/res/
- การวางซ้อนตัวแปรจากประเภทบิลด์และตัวแปรของบิลด์
- ทรัพยากร Dependency ของโปรเจ็กต์ห้องสมุด
Gradle จะผสานทรัพยากรที่ซ้ำกันตามลําดับความสําคัญแบบซ้อนทับต่อไปนี้
Dependency → Main → Build flavor → Build type
ตัวอย่างเช่น หากทรัพยากรที่ซ้ำกันปรากฏขึ้นทั้งในทรัพยากรหลักและเวอร์ชันบิลด์ Gradle จะเลือกทรัพยากรในเวอร์ชันบิลด์
หากมีทรัพยากรที่เหมือนกันปรากฏในชุดแหล่งที่มาเดียวกัน Gradle จะผสานไม่ได้และแสดงข้อผิดพลาดการผสานทรัพยากร กรณีนี้อาจเกิดขึ้นได้หากคุณกำหนดชุดแหล่งที่มาหลายชุดในพร็อพเพอร์ตี้ sourceSet
ของไฟล์ build.gradle.kts
เช่น หากทั้ง src/main/res/
และ src/main/res2/
มีทรัพยากรที่เหมือนกัน
สร้างความสับสนให้กับโค้ด
วัตถุประสงค์ของการปรับให้ยากต่อการอ่าน (Obfuscation) คือเพื่อลดขนาดแอปโดยการทำให้ชื่อคลาส เมธอด และช่องของแอปสั้นลง ต่อไปนี้เป็นตัวอย่างการสร้างความสับสนโดยใช้ R8
androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
android.content.Context mContext -> a
int mListItemLayout -> O
int mViewSpacingRight -> l
android.widget.Button mButtonNeutral -> w
int mMultiChoiceItemLayout -> M
boolean mShowTitle -> P
int mViewSpacingLeft -> j
int mButtonPanelSideLayout -> K
แม้ว่าการปรับให้ยากต่อการอ่าน (Obfuscation) ไม่ได้นำโค้ดออกจากแอป แต่การลดขนาดลงอย่างมากจะดูได้ในแอปที่มีไฟล์ DEX ที่จัดทำดัชนีคลาส เมธอด และฟิลด์ต่างๆ อย่างไรก็ตาม เนื่องจากการสร้างความสับสนจะเปลี่ยนชื่อส่วนต่างๆ ของโค้ด งานบางอย่าง เช่น การตรวจสอบสแต็กเทรซ จึงต้องใช้เครื่องมือเพิ่มเติม หากต้องการทําความเข้าใจสแต็กเทรซหลังจากการสร้างความสับสน ให้อ่านส่วนเกี่ยวกับวิธีถอดรหัสสแต็กเทรซที่ปรับให้ยากต่อการอ่าน (Obfuscate)
นอกจากนี้ หากโค้ดของคุณต้องใช้การตั้งชื่อที่คาดเดาได้สำหรับวิธีการและคลาสของแอป เช่น เมื่อใช้การสะท้อนความรู้สึก คุณควรถือว่าลายเซ็นเหล่านั้นเป็นจุดแรกเข้าและระบุกฎ Keep สำหรับโค้ดดังกล่าว ตามที่อธิบายไว้ในส่วนเกี่ยวกับวิธีปรับแต่งโค้ดที่จะเก็บไว้ กฎ keep เหล่านี้บอก R8 ไม่เพียงให้เก็บโค้ดนั้นไว้ใน DEX สุดท้ายของแอปเท่านั้น แต่ยังให้เก็บชื่อเดิมของโค้ดด้วย
ถอดรหัสสแต็กเทรซที่ปรับให้ยากต่อการอ่าน (Obfuscate)
หลังจาก R8 ทำให้โค้ดของคุณงง การทำความเข้าใจสแต็กเทรซจะยาก (หรือเป็นไปไม่ได้) เนื่องจากชื่อคลาสและเมธอดอาจเปลี่ยนไป หากต้องการดูสแต็กเทรซเดิม คุณควรติดตามสแต็กเทรซอีกครั้ง
การเพิ่มประสิทธิภาพโค้ด
R8 จะตรวจสอบโค้ดในระดับที่ลึกยิ่งขึ้นเพื่อนำโค้ดที่ไม่ได้ใช้งานออกมากขึ้น หรือเขียนโค้ดใหม่ให้สั้นลง (หากเป็นไปได้) เพื่อเพิ่มประสิทธิภาพแอปให้ดียิ่งขึ้น ตัวอย่างการเพิ่มประสิทธิภาพดังกล่าวมีดังนี้
- หากรหัสไม่ได้ใช้ Branch ของ
else {}
สําหรับคำสั่ง if/else ที่ระบุ R8 อาจนำรหัสของ Branch ของelse {}
ออก - หากโค้ดเรียกใช้เมธอดในบางตำแหน่งเท่านั้น R8 อาจนําเมธอดออกและฝังไว้ในตำแหน่งการเรียกใช้ไม่กี่แห่ง
- หาก R8 พิจารณาว่าคลาสมีคลาสย่อยที่ไม่ซ้ำกันเพียงคลาสเดียว และคลาสนั้นไม่ได้สร้างอินสแตนซ์ (เช่น คลาสฐานนามธรรมที่ใช้โดยคลาสการใช้งานที่เฉพาะเจาะจงเพียงคลาสเดียว) R8 จะรวมคลาส 2 คลาสเข้าด้วยกันและนำคลาสออกจากแอปได้
- หากต้องการดูข้อมูลเพิ่มเติม โปรดอ่านบล็อกโพสต์การเพิ่มประสิทธิภาพ R8 โดย Jake Wharton
R8 ไม่อนุญาตให้คุณปิดหรือเปิดใช้การเพิ่มประสิทธิภาพแบบแยกต่างหาก หรือแก้ไขลักษณะการทํางานของการเพิ่มประสิทธิภาพ อันที่จริง R8 ไม่สนใจกฎ ProGuard ที่พยายามแก้ไขการเพิ่มประสิทธิภาพเริ่มต้น เช่น -optimizations
และ -optimizationpasses
ข้อจำกัดนี้สำคัญเนื่องจากเมื่อ R8 ได้รับการปรับปรุงอย่างต่อเนื่อง การรักษาลักษณะการทำงานมาตรฐานสำหรับการเพิ่มประสิทธิภาพจะช่วยให้ทีม Android Studio แก้ปัญหาที่คุณอาจพบได้อย่างง่ายดาย
โปรดทราบว่าการเปิดใช้การเพิ่มประสิทธิภาพจะเปลี่ยนสแต็กเทรซสําหรับแอปพลิเคชัน เช่น การฝังโค้ดจะนําเฟรมสแต็กออก ดูส่วนการติดตามซ้ำเพื่อดูวิธีรับสแต็กเทรซเดิม
ผลกระทบต่อประสิทธิภาพรันไทม์
หากเปิดใช้การบีบอัด การสร้างความสับสน และการเพิ่มประสิทธิภาพทั้งหมด R8 จะปรับปรุงประสิทธิภาพรันไทม์ของโค้ด (รวมถึงเวลาเริ่มต้นและเวลาเฟรมในเธรด UI) สูงสุด 30% การปิดใช้รายการใดรายการหนึ่งเหล่านี้จะจำกัดชุดการเพิ่มประสิทธิภาพที่ R8 ใช้อย่างมาก
หากเปิดใช้ R8 คุณควรสร้างโปรไฟล์การเริ่มต้นด้วยเพื่อให้ประสิทธิภาพการเริ่มต้นดีขึ้น
เปิดใช้การเพิ่มประสิทธิภาพที่ปรับปรุงแล้ว
R8 มีชุดการเพิ่มประสิทธิภาพเพิ่มเติม (เรียกว่า "โหมดเต็มรูปแบบ") ซึ่งทําให้ทํางานแตกต่างจาก ProGuard ระบบจะเปิดใช้การเพิ่มประสิทธิภาพเหล่านี้โดยค่าเริ่มต้นตั้งแต่ปลั๊กอิน Android Gradle เวอร์ชัน 8.0.0
คุณปิดใช้การเพิ่มประสิทธิภาพเพิ่มเติมเหล่านี้ได้โดยใส่ค่าต่อไปนี้ในไฟล์ gradle.properties
ของโปรเจ็กต์
android.enableR8.fullMode=false
เนื่องจากการเพิ่มประสิทธิภาพเพิ่มเติมทำให้ R8 ทำงานต่างจาก ProGuard คุณจึงอาจต้องใส่กฎ ProGuard เพิ่มเติมเพื่อหลีกเลี่ยงปัญหารันไทม์หากคุณใช้กฎที่ออกแบบมาเพื่อ ProGuard เช่น สมมติว่าโค้ดของคุณอ้างอิงคลาสผ่าน Java Reflection API เมื่อไม่ได้ใช้ "โหมดเต็ม" R8 จะถือว่าคุณตั้งใจที่จะตรวจสอบและจัดการออบเจ็กต์ของคลาสนั้นๆ ขณะรันไทม์ แม้ว่าโค้ดของคุณจะไม่ทำเช่นนั้นก็ตาม และจะเก็บคลาสและตัวเริ่มต้นแบบคงที่ไว้โดยอัตโนมัติ
อย่างไรก็ตาม เมื่อใช้ "โหมดเต็มรูปแบบ" R8 จะไม่ทำสมมติฐานนี้ และหาก R8 ยืนยันว่าโค้ดของคุณไม่เคยใช้คลาสดังกล่าวที่รันไทม์ ก็จะนำคลาสออกจาก DEX สุดท้ายของแอป กล่าวคือ หากต้องการเก็บคลาสและตัวเริ่มต้นแบบคงที่ของคลาสไว้ คุณต้องใส่กฎการเก็บรักษาไว้ในไฟล์กฎ
หากพบปัญหาขณะใช้ "โหมดเต็ม" ของ R8 โปรดดูหน้าคำถามที่พบบ่อยของ R8 เพื่อดูวิธีแก้ปัญหาที่เป็นไปได้ หากแก้ปัญหาไม่ได้ โปรดรายงานข้อบกพร่อง
การติดตามสแต็กเทรซย้อนหลัง
โค้ดที่ประมวลผลโดย R8 มีการเปลี่ยนแปลงด้วยวิธีต่างๆ ที่ทำให้สแต็กเทรซเข้าใจได้ยากขึ้นเนื่องจากสแต็กเทรซไม่สอดคล้องกับซอร์สโค้ดทุกประการ กรณีนี้อาจเกิดขึ้นเมื่อมีการแก้ไขหมายเลขบรรทัดเมื่อไม่ได้เก็บข้อมูลการแก้ไขข้อบกพร่องไว้ ซึ่งอาจเกิดจากการเพิ่มประสิทธิภาพ เช่น การแทรกบรรทัดและการสรุป ปัจจัยที่ส่งผลมากที่สุดคือการปรับแต่งโค้ด ซึ่งแม้แต่คลาสและเมธอดก็จะมีการเปลี่ยนแปลงชื่อ
หากต้องการกู้คืนสแต็กเทรซเดิม R8 มีเครื่องมือบรรทัดคำสั่งretrace ซึ่งรวมอยู่ในแพ็กเกจเครื่องมือบรรทัดคำสั่ง
หากต้องการรองรับการติดตามสแต็กเทรซของแอปพลิเคชันอีกครั้ง คุณควรตรวจสอบว่าบิลด์เก็บข้อมูลไว้เพียงพอที่จะติดตามอีกครั้งโดยเพิ่มกฎต่อไปนี้ลงในไฟล์ proguard-rules.pro
ของโมดูล
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
แอตทริบิวต์ LineNumberTable
จะเก็บข้อมูลตำแหน่งไว้ในวิธีการที่พิมพ์ตำแหน่งเหล่านั้นในสแต็กเทรซ แอตทริบิวต์ SourceFile
ช่วยให้มั่นใจได้ว่ารันไทม์ที่เป็นไปได้ทั้งหมดจะพิมพ์ข้อมูลตำแหน่งจริง
คำสั่ง -renamesourcefileattribute
จะตั้งค่าชื่อไฟล์ต้นฉบับในการติดตามกองซ้อนเป็น SourceFile
เท่านั้น คุณไม่จำเป็นต้องใช้ชื่อไฟล์ต้นฉบับจริงเมื่อทำการติดตามอีกครั้ง เนื่องจากไฟล์การแมปมีไฟล์ต้นฉบับอยู่
R8 สร้างไฟล์ mapping.txt
ทุกครั้งที่เรียกใช้ ซึ่งมีข้อมูลที่จำเป็นต่อการแมปสแต็กเทรซย้อนกลับไปยังสแต็กเทรซดั้งเดิม Android Studio จะบันทึกไฟล์ไว้ในไดเรกทอรี <module-name>/build/outputs/mapping/<build-type>/
เมื่อเผยแพร่แอปใน Google Play คุณสามารถอัปโหลดไฟล์ mapping.txt
ให้กับแอปแต่ละเวอร์ชันได้ เมื่อเผยแพร่โดยใช้ Android App Bundle ระบบจะรวมไฟล์นี้ไว้ในเนื้อหา App Bundle โดยอัตโนมัติ จากนั้น Google Play จะติดตามสแต็กเทรซที่เข้ามาใหม่จากปัญหาที่ผู้ใช้รายงาน เพื่อให้คุณตรวจสอบปัญหาใน Play Console ได้ ดูข้อมูลเพิ่มเติมได้ที่บทความในศูนย์ช่วยเหลือเกี่ยวกับวิธีถอดรหัสซอร์สโค้ดที่สร้างความสับสน
แก้ปัญหาด้วย R8
ส่วนนี้จะอธิบายกลยุทธ์บางอย่างในการแก้ปัญหาเมื่อเปิดใช้การบีบอัด การสร้างความสับสน และการเพิ่มประสิทธิภาพโดยใช้ R8 หากไม่พบวิธีแก้ปัญหาด้านล่าง โปรดอ่านหน้าคำถามที่พบบ่อยของ R8 และคู่มือการแก้ปัญหาของ ProGuard
สร้างรายงานโค้ดที่นำออก (หรือเก็บไว้)
การดูรายงานของโค้ดทั้งหมดที่ R8 นำออกจากแอปของคุณอาจมีประโยชน์ในการช่วยแก้ปัญหา R8 ของคุณ สำหรับแต่ละโมดูลที่คุณต้องการสร้างรายงานนี้ ให้เพิ่ม -printusage <output-dir>/usage.txt
ลงในไฟล์กฎที่กำหนดเอง เมื่อคุณเปิดใช้ R8 และสร้างแอป R8 จะแสดงผลรายงานที่มีเส้นทางและชื่อไฟล์ที่คุณระบุ รายงานโค้ดที่ถูกนําออกจะมีลักษณะคล้ายกับตัวอย่างต่อไปนี้
androidx.drawerlayout.R$attr
androidx.vectordrawable.R
androidx.appcompat.app.AppCompatDelegateImpl
public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
public boolean hasWindowFeature(int)
public void setHandleNativeActionModesEnabled(boolean)
android.view.ViewGroup getSubDecor()
public void setLocalNightMode(int)
final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
private static final boolean DEBUG
private static final java.lang.String KEY_LOCAL_NIGHT_MODE
static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
...
หากต้องการดูรายงานของจุดแรกเข้าที่ R8 กำหนดจากกฎการเก็บของโปรเจ็กต์ ให้ใส่ -printseeds <output-dir>/seeds.txt
ในไฟล์กฎที่กำหนดเอง เมื่อคุณเปิดใช้ R8 และสร้างแอปแล้ว R8 จะแสดงรายงานพร้อมเส้นทางและชื่อไฟล์ที่คุณระบุ รายงานของจุดเข้าชมที่เก็บไว้จะมีลักษณะคล้ายกับตัวอย่างต่อไปนี้
com.example.myapplication.MainActivity
androidx.appcompat.R$layout: int abc_action_menu_item_layout
androidx.appcompat.R$attr: int activityChooserViewStyle
androidx.appcompat.R$styleable: int MenuItem_android_id
androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
androidx.lifecycle.FullLifecycleObserverAdapter
...
แก้ปัญหาการลดขนาดทรัพยากร
เมื่อคุณลดขนาดทรัพยากร หน้าต่างสร้าง
จะแสดงสรุปของทรัพยากรที่ถูกนำออกจากแอป (คุณต้องคลิกสลับมุมมอง
ทางด้านซ้ายของหน้าต่างก่อนเพื่อแสดงเอาต์พุตข้อความแบบละเอียดจาก Gradle) เช่น
:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning
นอกจากนี้ Gradle จะสร้างไฟล์การวินิจฉัยชื่อ resources.txt
ใน <module-name>/build/outputs/mapping/release/
(โฟลเดอร์เดียวกับไฟล์เอาต์พุตของ ProGuard) ไฟล์นี้มีรายละเอียด เช่น ทรัพยากรที่อ้างอิงทรัพยากรอื่นๆ และทรัพยากรที่ใช้หรือนําออก
ตัวอย่างเช่น หากต้องการดูสาเหตุที่ @drawable/ic_plus_anim_016
ยังคงอยู่ในแอป ให้เปิดไฟล์ resources.txt
แล้วค้นหาชื่อไฟล์นั้น คุณอาจพบว่ามีการอ้างอิงจากแหล่งข้อมูลอื่น ดังนี้
16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out] @drawable/ic_plus_anim_016
ตอนนี้คุณต้องทราบว่าเหตุใด @drawable/add_schedule_fab_icon_anim
จึงเข้าถึงได้ และหากค้นหาขึ้นด้านบน คุณจะเห็นทรัพยากรนั้นแสดงอยู่ใต้ "ทรัพยากรที่เข้าถึงได้ของรูทมีดังนี้" ซึ่งหมายความว่ามีโค้ดอ้างอิงถึง add_schedule_fab_icon_anim
(นั่นคือพบรหัส R.drawable ของ add_schedule_fab_icon_anim
ในโค้ดที่เข้าถึงได้)
หากคุณไม่ได้ใช้การตรวจสอบแบบเข้มงวด ระบบจะทำเครื่องหมายรหัสทรัพยากรว่าเข้าถึงได้หากมีสตริงคงที่ที่ดูเหมือนว่าอาจใช้สร้างชื่อทรัพยากรสําหรับทรัพยากรที่โหลดแบบไดนามิก ในกรณีนี้ หากคุณค้นหาชื่อทรัพยากรในเอาต์พุตการสร้าง คุณอาจเห็นข้อความดังต่อไปนี้
10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
used because it format-string matches string pool constant ic_plus_anim_%1$d.
หากคุณเห็นสตริงใดสตริงหนึ่งและมั่นใจว่าไม่ได้ใช้สตริงเพื่อโหลดทรัพยากรที่ระบุแบบไดนามิก คุณสามารถใช้แอตทริบิวต์ tools:discard
เพื่อแจ้งให้ระบบบิลด์นำสตริงออก ตามที่อธิบายไว้ในส่วนเกี่ยวกับวิธีปรับแต่งทรัพยากรที่จะเก็บไว้