คุณควรเพิ่มประสิทธิภาพและลดขนาดบิลด์รุ่นด้วย isMinifyEnabled = true
เพื่อให้แอปมีขนาดเล็กและรวดเร็วที่สุด
ซึ่งจะเป็นการเปิดใช้การลดขนาด ซึ่งจะนำโค้ดที่ไม่ได้ใช้งานออก การสร้างความสับสน ซึ่งจะย่อชื่อคลาสและสมาชิกของแอปให้สั้นลง และการเพิ่มประสิทธิภาพ ซึ่งจะใช้กลยุทธ์การเพิ่มประสิทธิภาพโค้ดที่ปรับปรุงแล้วเพื่อลดขนาดและปรับปรุงประสิทธิภาพของแอปให้ดียิ่งขึ้น หน้านี้จะอธิบายวิธีที่ R8 ทำงานเหล่านี้เมื่อคอมไพล์โปรเจ็กต์ของคุณ และวิธีปรับแต่ง
เมื่อคุณสร้างโปรเจ็กต์โดยใช้ Android Gradle Plugin 3.4.0 ขึ้นไป ปลั๊กอินจะไม่ใช้ ProGuard เพื่อเพิ่มประสิทธิภาพโค้ดขณะคอมไพล์อีกต่อไป แต่จะใช้ร่วมกับคอมไพเลอร์ R8 เพื่อจัดการงานต่อไปนี้ในรันไทม์
- การย่อโค้ด (หรือ Tree-shaking): ตรวจหาและนําคลาส ฟิลด์ เมธอด และแอตทริบิวต์ที่ไม่ได้ใช้ออกจากแอปและไลบรารีที่เกี่ยวข้องอย่างปลอดภัย (จึงเป็นเครื่องมือที่มีประโยชน์ในการแก้ปัญหาขีดจํากัดการอ้างอิง 64,000) เช่น หากคุณใช้ API เพียงไม่กี่รายการของไลบรารี เครื่องมือจะระบุโค้ดไลบรารีที่แอปไม่ได้ใช้และนำเฉพาะโค้ดนั้นออกจากแอปได้ ดูข้อมูลเพิ่มเติมได้ในส่วนวิธีลดขนาดโค้ด
- การลดขนาดทรัพยากร: นำทรัพยากรที่ไม่ได้ใช้ออกจากแอปที่แพ็ก รวมถึงทรัพยากรที่ไม่ได้ใช้ใน Dependency ของไลบรารีของแอป ซึ่งทำงานร่วมกับการลดขนาดโค้ด เช่น เมื่อนำโค้ดที่ไม่ได้ใช้งานออกแล้ว ระบบจะนำทรัพยากรที่ไม่ได้อ้างอิงอีกต่อไปออกอย่างปลอดภัยด้วย ดูข้อมูลเพิ่มเติมได้ที่ส่วนวิธีลดขนาดทรัพยากร
- การเพิ่มประสิทธิภาพ: ตรวจสอบและเขียนโค้ดใหม่เพื่อปรับปรุงประสิทธิภาพรันไทม์และลดขนาดไฟล์ DEX ของแอปให้น้อยลง ซึ่งจะปรับปรุงประสิทธิภาพรันไทม์ของโค้ดได้สูงสุด 30% ซึ่งจะปรับปรุงเวลาเริ่มต้นและเฟรมได้อย่างมาก ตัวอย่างเช่น หาก R8 ตรวจพบว่าไม่มีการใช้สาขา
else {}
ของคำสั่ง if/else หนึ่งๆ เลย R8 จะนําโค้ดสําหรับสาขา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 ระบบจะไม่เปิดใช้การบีบอัด การสร้างความสับสน และการเพิ่มประสิทธิภาพโค้ดโดยค่าเริ่มต้น เนื่องจากการเพิ่มประสิทธิภาพเวลาคอมไพล์เหล่านี้จะเพิ่มเวลาสร้างโปรเจ็กต์และอาจทำให้เกิดข้อบกพร่องหากคุณปรับแต่งโค้ดที่จะเก็บไว้ไม่เพียงพอ
ดังนั้น คุณควรเปิดใช้งานเหล่านี้เมื่อคอมไพล์แอปเวอร์ชันสุดท้ายที่จะทดสอบก่อนเผยแพร่ หากต้องการเปิดใช้การบีบอัด การสร้างความสับสน และการเพิ่มประสิทธิภาพ ให้ใส่ข้อมูลต่อไปนี้ในสคริปต์การสร้างระดับโปรเจ็กต์
Kotlin
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" ) } } ... }
Groovy
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 นอกจากตำแหน่งเหล่านี้แล้ว ปลั๊กอิน Gradle ของ Android เวอร์ชัน 3.6 ขึ้นไปยังรองรับกฎการบีบอัดแบบกำหนดเป้าหมายด้วย |
หากเผยแพร่ไลบรารี AAR หรือ JAR ด้วยไฟล์กฎของตนเอง และคุณรวมไลบรารีนั้นไว้เป็น Dependency ของเวลาคอมไพล์ R8 จะใช้กฎเหล่านั้นโดยอัตโนมัติเมื่อคอมไพล์โปรเจ็กต์ นอกจากกฎ ProGuard แบบดั้งเดิมแล้ว ปลั๊กอิน Android Gradle เวอร์ชัน 3.6 ขึ้นไปยังรองรับกฎการบีบอัดแบบกำหนดเป้าหมายด้วย กฎเหล่านี้เป็นกฎที่กำหนดเป้าหมายไปยังเครื่องมือบีบอัดที่เฉพาะเจาะจง (R8 หรือ ProGuard) รวมถึงเวอร์ชันเครื่องมือบีบอัดที่เฉพาะเจาะจง การใช้ไฟล์กฎที่รวมอยู่ในไลบรารีจะมีประโยชน์หากต้องใช้กฎบางอย่างเพื่อให้ไลบรารีทํางานได้อย่างถูกต้อง กล่าวคือ นักพัฒนาไลบรารีได้ดําเนินการตามขั้นตอนการแก้ปัญหาให้คุณแล้ว อย่างไรก็ตาม โปรดทราบว่าเนื่องจากกฎเป็นการเพิ่ม คุณจึงนำกฎบางอย่างที่ไลบรารีที่อ้างอิงมีไว้ออกไม่ได้ และอาจส่งผลต่อการคอมไพล์ส่วนอื่นๆ ของแอป เช่น หากไลบรารีมีกฎในการปิดใช้การเพิ่มประสิทธิภาพโค้ด กฎนั้นจะปิดใช้การเพิ่มประสิทธิภาพสำหรับทั้งโปรเจ็กต์ |
Android Asset Package Tool 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 จะใช้กฎที่อยู่ในไดเรกทอรี r8-from-8.0.0-upto-8.2.0
ตั้งแต่เวอร์ชัน 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 ทดสอบเท่านั้นได้ ดังนี้
Kotlin
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") } } }
Groovy
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 จะนําโค้ดนั้นออกเมื่อลดขนาดแอป
R8 จะกำหนดจุดแรกเข้าผ่านกฎ -keep
ในไฟล์การกำหนดค่า R8 ของโปรเจ็กต์ กล่าวคือ กฎการเก็บรักษาจะระบุคลาสที่ R8 ไม่ควรทิ้งเมื่อลดขนาดแอป และ R8 จะถือว่าคลาสเหล่านั้นเป็นจุดเข้าใช้งานที่เป็นไปได้ของแอป ปลั๊กอิน Android Gradle และ AAPT2 จะสร้างกฎการเก็บรักษาที่จําเป็นสําหรับโปรเจ็กต์แอปส่วนใหญ่ให้คุณโดยอัตโนมัติ เช่น กิจกรรม มุมมอง และบริการของแอป อย่างไรก็ตาม หากต้องการปรับแต่งลักษณะการทํางานเริ่มต้นนี้ด้วยกฎการเก็บรักษาเพิ่มเติม โปรดอ่านส่วนเกี่ยวกับวิธีปรับแต่งโค้ดที่จะเก็บ
แต่หากสนใจเฉพาะการลดขนาดทรัพยากรของแอป ให้ข้ามไปที่ส่วนเกี่ยวกับวิธีลดขนาดทรัพยากร
โปรดทราบว่าหากมีการลดขนาดโปรเจ็กต์ไลบรารี แอปที่ขึ้นกับไลบรารีนั้นจะมีคลาสไลบรารีที่ลดขนาด คุณอาจต้องปรับกฎการเก็บรักษาคลังหากมีคลาสที่หายไปใน 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 ระดับนี้รองรับรายการที่ลบไปแล้ว - ใช้
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
สำหรับการลดขนาดโค้ด) เช่น
Kotlin
android { ... buildTypes { getByName("release") { isShrinkResources = true isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } }
Groovy
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_
ว่าใช้แล้ว
Kotlin
val name = String.format("img_%1d", angle + 1) val res = resources.getIdentifier(name, "drawable", packageName)
Java
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
ระบบจะนำทรัพยากรสำหรับภาษาที่ไม่ได้ระบุออก
ข้อมูลโค้ดต่อไปนี้แสดงวิธีจำกัดทรัพยากรภาษาไว้ที่ภาษาอังกฤษและฝรั่งเศสเท่านั้น
Kotlin
android { defaultConfig { ... resourceConfigurations.addAll(listOf("en", "fr")) } }
Groovy
android { defaultConfig { ... resConfigs "en", "fr" } }
เมื่อเผยแพร่แอปโดยใช้รูปแบบ App Bundle ของ Android ระบบจะดาวน์โหลดเฉพาะภาษาที่กําหนดค่าไว้ในอุปกรณ์ของผู้ใช้เมื่อติดตั้งแอป โดยค่าเริ่มต้น ในทํานองเดียวกัน ระบบจะรวมเฉพาะทรัพยากรที่ตรงกับความหนาแน่นของหน้าจอของอุปกรณ์ และไลบรารีแบบเนทีฟที่ตรงกับ ABI ของอุปกรณ์ไว้ในการดาวน์โหลด ดูข้อมูลเพิ่มเติมได้ที่การกำหนดค่า Android App Bundle
สำหรับแอปเดิมที่เผยแพร่ด้วย APK (สร้างขึ้นก่อนเดือนสิงหาคม 2021) คุณสามารถปรับแต่งความละเอียดของหน้าจอหรือทรัพยากร ABI ที่จะรวมไว้ใน APK ได้โดยสร้าง APK หลายรายการที่แต่ละรายการกำหนดเป้าหมายเป็นการกำหนดค่าอุปกรณ์ที่แตกต่างกัน
ผสานทรัพยากรที่ซ้ำกัน
โดยค่าเริ่มต้น Gradle จะผสานทรัพยากรที่มีชื่อเหมือนกันด้วย เช่น ไฟล์ Drawable ที่มีชื่อเดียวกันซึ่งอาจอยู่ในโฟลเดอร์ทรัพยากรที่แตกต่างกัน ลักษณะการทํางานนี้ไม่ได้ควบคุมโดยพร็อพเพอร์ตี้ 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
แม้ว่าการปรับให้ยากต่อการอ่านจะไม่นำโค้ดออกจากแอป แต่คุณจะเห็นว่าแอปที่มีไฟล์ DEX ซึ่งจัดทำดัชนีคลาส เมธอด และช่องจำนวนมากจะมีขนาดลดลงอย่างมาก อย่างไรก็ตาม เนื่องจากการสร้างความสับสนจะเปลี่ยนชื่อส่วนต่างๆ ของโค้ด งานบางอย่าง เช่น การตรวจสอบสแต็กเทรซ จึงต้องใช้เครื่องมือเพิ่มเติม หากต้องการทําความเข้าใจสแต็กเทรซหลังจากการสร้างความสับสน ให้อ่านส่วนเกี่ยวกับวิธีถอดรหัสสแต็กเทรซที่ปรับให้ยากต่อการอ่าน (Obfuscate)
นอกจากนี้ หากโค้ดของคุณใช้การตั้งชื่อที่คาดเดาได้สำหรับเมธอดและคลาสของแอป เช่น เมื่อใช้การสะท้อนกลับ คุณควรถือว่าลายเซ็นเหล่านั้นเป็นจุดแรกเข้าและระบุกฎการเก็บไว้สำหรับลายเซ็นเหล่านั้น ตามที่อธิบายไว้ในส่วนเกี่ยวกับวิธีปรับแต่งโค้ดที่จะเก็บไว้ กฎ keep เหล่านี้บอกให้ R8 ไม่เพียงเก็บโค้ดนั้นไว้ใน DEX สุดท้ายของแอปเท่านั้น แต่ยังเก็บชื่อเดิมของโค้ดด้วย
ถอดรหัสสแต็กเทรซที่ปรับให้ยากต่อการอ่าน
หลังจาก R8 ทำให้โค้ดของคุณงง การทำความเข้าใจสแต็กเทรซจะยาก (หรือเป็นไปไม่ได้) เนื่องจากชื่อคลาสและเมธอดอาจเปลี่ยนไป หากต้องการดูสแต็กเทรซเดิม คุณควรติดตามสแต็กเทรซอีกครั้ง
การเพิ่มประสิทธิภาพโค้ด
R8 จะตรวจสอบโค้ดในระดับที่ลึกยิ่งขึ้นเพื่อนำโค้ดที่ไม่ได้ใช้งานออกมากขึ้น หรือเขียนโค้ดใหม่ให้สั้นลง (หากเป็นไปได้) เพื่อเพิ่มประสิทธิภาพแอปให้ดียิ่งขึ้น ตัวอย่างการเพิ่มประสิทธิภาพดังกล่าวมีดังนี้
- หากโค้ดของคุณไม่เคยใช้สาขา
else {}
สําหรับคำสั่ง if/else ที่ระบุ R8 อาจนําโค้ดสําหรับสาขา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
เพื่อแจ้งให้ระบบบิลด์นำสตริงนั้นออก ตามที่อธิบายไว้ในส่วนเกี่ยวกับวิธีปรับแต่งทรัพยากรที่จะเก็บไว้