เพิ่มประสิทธิภาพโค้ดและทรัพยากรของแพลตฟอร์มด้วย R8

ระบบบิลด์แพลตฟอร์ม Android (Soong) จะเรียกใช้คอมไพเลอร์ R8 ในเป้าหมายที่คอมไพล์ไบต์โค้ด DEX ซึ่งรวมถึงแอป Android (android_app), การทดสอบ (android_test) และไลบรารี Java ที่ติดตั้งได้ซึ่งมี compile_dex: true (เช่น services.jar) เพื่อลดขนาด เพิ่มประสิทธิภาพ และตัดโค้ดและทรัพยากรที่ไม่ได้ใช้ออก ในไลบรารีแบบคงที่ (android_library และ java_library แบบคงที่) Soong จะไม่เรียกใช้ R8 โดยตรง แต่จะใช้บล็อกพร็อพเพอร์ตี้ optimize เพื่อแนบและเผยแพร่กฎการเก็บรักษาของผู้บริโภค (export_proguard_flags_files: true) ไปยังเป้าหมาย DEX ปลายทางที่ลิงก์แบบคงที่

สำหรับวิศวกรแพลตฟอร์มที่สร้างอิมเมจระบบหรือแพ็กเกจของผู้ให้บริการ การกำจัดกฎการเก็บรักษาที่กว้างเกินไปจะให้ประโยชน์ด้านประสิทธิภาพของระบบโดยตรง ดังนี้

  • พื้นที่ในพาร์ติชันระบบน้อยลง: R8 จะนำคลาส เมธอด และทรัพยากรที่ไม่ได้ใช้งานออกก่อนที่จะแพ็กเกจ APK และ JAR ลงใน /system, /system_ext, /product และ /vendor
  • อาร์ติแฟกต์ที่คอมไพล์แล้วมีขนาดเล็กลง: เมธอด DEX ที่น้อยลงหมายความว่าไฟล์ .odex และ .vdex ที่สร้างโดย dex2oat ในระหว่างการคอมไพล์ขณะบิลด์หรือในอุปกรณ์จะมีขนาดเล็กลง
  • ลดแรงกดดันด้านหน่วยความจำรันไทม์: เนื่องจาก Android แมป .odex, .vdex และตารางทรัพยากร APK ลงในหน่วยความจำของกระบวนการโดยใช้การเรียก mmap ที่มีการแบ่งหน้าตามความต้องการ ไบนารีที่มีขนาดเล็กลงจึงช่วยลดข้อผิดพลาดของหน้าหลักในระหว่างการเริ่มต้นแอป และลดหน่วยความจำของโค้ดและทรัพยากรที่อยู่ในกระบวนการต่างๆ ดูข้อมูลเพิ่มเติมเกี่ยวกับผลกระทบของโค้ดแอปที่คอมไพล์แล้วต่อหน่วยความจำของอุปกรณ์ได้ที่โค้ดแอปคือหน่วยความจำ
  • การเพิ่มประสิทธิภาพทั้งโปรแกรมที่มีประสิทธิภาพมากขึ้น: ข้อจำกัดการเก็บรักษาที่แคบลงช่วยให้ R8 สามารถแทรกเมธอดแบบอินไลน์, ยกเลิกการเรียกใช้เมธอดแบบเสมือนและอินเทอร์เฟซ, ตัดฟิลด์ที่ไม่ได้ใช้ออก และเผยแพร่ค่าคงที่ในคลาสต่างๆ

กำหนดค่าการเพิ่มประสิทธิภาพ R8 ใน Soong

ในไฟล์ Android.bp ให้กำหนดค่าการเพิ่มประสิทธิภาพ R8 โดยใช้บล็อกพร็อพเพอร์ตี้ optimize ในเป้าหมาย android_app และ java_library ที่ติดตั้งได้ (หรือในโมดูล android_library และ java_library แบบคงที่เพื่อส่งออกกฎการเก็บรักษาของผู้บริโภค)

android_app {
    name: "MySystemApp",
    srcs: ["src/**/*.java"],
    optimize: {
        obfuscate: true,
        shrink_resources: true,
    },
}

ตารางต่อไปนี้สรุปพร็อพเพอร์ตี้ optimize ที่พบบ่อยที่สุดใน Soong (กำหนดไว้ใน build/soong/java/dex.go)

พร็อพเพอร์ตี้ คำอธิบาย
enabled ควบคุมว่าจะเรียกใช้ R8 ในเป้าหมายหรือไม่ ค่าเริ่มต้นคือ true สำหรับเป้าหมาย android_app ทั้งหมด
shrink ควบคุมการทำ Tree Shaking เพื่อนำคลาส ฟิลด์ และเมธอดที่เข้าถึงไม่ได้ออก ค่าเริ่มต้นคือ true สำหรับเป้าหมาย android_app (false สำหรับ java_library แบบสแตนด์อโลนและโมดูลทดสอบ ซึ่งจะส่ง -dontshrink เว้นแต่จะตั้งค่าไว้อย่างชัดเจน)
optimize ควบคุมการเพิ่มประสิทธิภาพไบต์โค้ด เช่น การแทรกเมธอด การผสานคลาส การเผยแพร่ค่าคงที่ และการนำสาขาที่ไม่ได้ใช้งานออก ค่าเริ่มต้นคือ true สำหรับเป้าหมาย android_app (ควบคุมโดยแฟล็กบิลด์ที่เผยแพร่ RELEASE_R8_OPTIMIZE_BY_DEFAULT)
obfuscate ควบคุมการลดขนาดและการเปลี่ยนชื่อตัวระบุ ค่าเริ่มต้นคือ false เพื่อความเข้ากันได้กับเวอร์ชันก่อนหน้า แต่ให้ตั้งค่าเป็น true ทุกครั้งที่ทำได้เพื่อลดขนาด DEX และปลดล็อกการเพิ่มประสิทธิภาพที่ลึกซึ้งยิ่งขึ้น (ดูเปิดใช้การปรับให้ยากต่อการอ่านทุกครั้งที่ทำได้)
shrink_resources นำทรัพยากรที่ไม่ได้ใช้ (res/ รายการ) ออกจาก APK ที่แพ็กเกจแล้วหลังจากการลดขนาดโค้ด ค่าเริ่มต้นคือ false
optimized_shrink_resources เรียกใช้ไปป์ไลน์เครื่องมือลดขนาดโค้ดและทรัพยากร R8 แบบผสานรวมเพื่อให้ R8 ติดตามการอ้างอิงโค้ดและทรัพยากรร่วมกันในรอบเดียว เมื่อตั้งค่า shrink_resources: true แล้ว ค่าเริ่มต้นจะเป็น RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT (true ในบิลด์แพลตฟอร์มมาตรฐาน)
proguard_flags_files แสดงรายการไฟล์ .flags หรือ .pro เฉพาะโมดูลที่มีกฎการเก็บรักษาที่กำหนดเอง หลีกเลี่ยงการเพิ่มไฟล์ที่กำหนดเองเมื่อคำอธิบายประกอบมาตรฐานหรือค่าเริ่มต้นเพียงพอแล้ว
export_proguard_flags_files เผยแพร่ proguard_flags_files ของไลบรารีนี้ไปยังโมดูลดาวน์สตรีมที่ขึ้นอยู่กับไลบรารีนี้แบบคงที่
trace_references_from แสดงรายการเป้าหมายที่ขึ้นอยู่กับการคอมไพล์ DEX โดยตรง (เชื่อมต่อผ่าน instrumentation_for หรือ libs) ซึ่งไบต์โค้ดอ้างอิงไปยังเป้าหมายนี้ R8 จะติดตามและเก็บรักษาโดยอัตโนมัติ (รวมถึงเผยแพร่ไปยังตัวแปร override_android_app และ override_android_test และรองรับไลบรารี Java ที่เป็นคู่กันซึ่งไม่ขึ้นอยู่กับเป้าหมายเมื่อจับคู่กับ .impl แยก)
allow_traced_references อนุญาตให้เป้าหมายที่ขึ้นอยู่กับการคอมไพล์ DEX โดยตรง (เชื่อมต่อผ่าน instrumentation_for หรือ libs เช่น การทดสอบข้ามที่เก็บหรือการทดสอบของผู้ให้บริการที่แสดงใน trace_references_from ไม่ได้) เลือกใช้การติดตามการอ้างอิงไบต์โค้ดไปยังเป้าหมายนี้โดยตั้งค่า trace_references: true
trace_references เมื่อตั้งค่าเป็น true ในทรัพยากรที่ขึ้นอยู่กับการคอมไพล์ DEX โดยตรงของเป้าหมายที่มี allow_traced_references: true จะเรียกใช้ R8 tracereferences ในโมดูลนี้และส่งกฎการเก็บรักษาที่สร้างขึ้นไปยังโมดูลเป้าหมาย
protect_api_surface ส่ง --protect-api-surface ไปยัง R8 เมื่อตั้งค่าใน java_sdk_library ระบบจะสร้างและแนบกฎการเก็บ Metalava API โดยอัตโนมัติในขอบเขต API ที่เปิดใช้ทั้งหมด (สาธารณะ, ระบบ, module-lib, system-server และทดสอบ) ไปยังไลบรารีการติดตั้งใช้งาน

ไลบรารีแบบคงที่ (android_library, java_library และการนำเข้า AAR) ยังสามารถตั้งค่า required_downstream_optimization: { enabled: true, optimize: true } ใน Android.bp เพื่อกำหนดให้เป้าหมายการคอมไพล์ DEX ปลายทางที่ลิงก์แบบคงที่ (โดยตรงหรือโดยอ้อม) ต้องเปิดใช้การลดขนาด R8 และการเพิ่มประสิทธิภาพไบต์โค้ด Soong จะเปิดใช้ฟีเจอร์นี้โดยอัตโนมัติใน androidx.compose.runtime_runtime เพื่อให้เป้าหมายที่ลิงก์ Jetpack Compose ได้รับการเพิ่มประสิทธิภาพด้วย R8

เปิดใช้การสร้างความสับสนทุกที่ที่ทำได้

ใน Soong obfuscate จะมีค่าเริ่มต้นเป็น false เพื่อความเข้ากันได้กับเวอร์ชันก่อนหน้า แต่คุณควรตั้งค่า obfuscate: true ในเป้าหมาย android_app และ DEX แบบสแตนด์อโลนอย่างชัดเจนทุกครั้งที่ทำได้ ดังนี้

  • ขนาด DEX และ .vdex ที่เล็กลง: การเปลี่ยนชื่อแพ็กเกจ คลาส ฟิลด์ และเมธอดเป็นตัวระบุแบบสั้น (a, b) จะลดขนาดพูลสตริง DEX (string_ids และ string_data_item) และตัวบ่งชี้ประเภท เนื่องจาก Android แมปไฟล์ .vdex และ .odex ลงในหน่วยความจำของกระบวนการ ตารางสัญลักษณ์ที่เล็กลงจึงลดทั้งขนาดพาร์ติชันระบบและหน่วยความจำที่ใช้ขณะรันไทม์โดยตรง
  • การเพิ่มประสิทธิภาพทั้งโปรแกรมที่ลึกซึ้งยิ่งขึ้น: การอนุญาตให้ R8 เปลี่ยนชื่อตัวระบุจะช่วยปลดล็อกการผสานคลาส การทำให้แพ็กเกจเป็นแบบแบน และการกรองข้อมูลที่ซ้ำกันออกของคลาสสังเคราะห์ในแพ็กเกจต่างๆ ที่ R8 ต้องข้ามไปเพื่อหลีกเลี่ยงการชนกันของชื่อ
  • การแทนที่ด้วยสัญลักษณ์สแต็กเทรซแบบเต็ม: การเปิดใช้การปรับให้ยากต่อการอ่าน (Obfuscation) ใน Soong ไม่ได้ลดความสามารถในการแก้ไขข้อบกพร่อง สำหรับเป้าหมายที่คอมไพล์ด้วย R8 ทุกรายการ Soong จะสร้างไฟล์การแมป proguard_dictionary ในไดเรกทอรีระดับกลางของโมดูล รวมพจนานุกรมโมดูลทั้งหมดไว้ในอาร์ติแฟกต์ proguard-dict.zip ของบิลด์ ฝังแฮชการแมป (--map-id-template) ไว้ในส่วนหัว DEX และเขียนแอตทริบิวต์ SourceFile ของคลาส (--source-file-template) ใหม่เพื่อให้ retrace สามารถสร้างสัญลักษณ์สแต็กเทรซได้โดยอัตโนมัติ

เก็บ obfuscate: false (หรือเก็บชื่อ API สาธารณะไว้อย่างชัดเจน) ไว้สำหรับสิ่งต่อไปนี้เท่านั้น

  • ไลบรารีที่ใช้ร่วมกันใน bootclasspath หรือ system_server classpath: โมดูล (java_library หรือ java_sdk_library) ที่แสดง API ภายนอก ซึ่งลิงก์แบบไดนามิกโดยโมดูลอื่นๆ ขณะรันไทม์ (เช่น เป้าหมาย framework.jar, services.jar หรือ <uses-library>) ต้อง เก็บ obfuscate: false ไว้หรือรักษา API ของตนไว้อย่างชัดเจนโดยใช้กฎ protect_api_surface: true, @KeepForApi หรือ -keep เพื่อให้ผู้เรียกที่คอมไพล์กับ Stub สามารถแก้ไขชื่อคลาสและสมาชิก ขณะรันไทม์ได้ ในโมดูล java_sdk_library การตั้งค่า protect_api_surface: true จะสร้างและแนบกฎการเก็บ Metalava ProGuard โดยอัตโนมัติในขอบเขต API ที่เปิดใช้ทั้งหมด (สาธารณะ, ระบบ, module-lib, system-server และการทดสอบ) ไปยังไลบรารีการติดตั้งใช้งาน เมื่อเปิดใช้การเพิ่มประสิทธิภาพ R8 ทั่วไปในเป้าหมาย bootclasspath ที่อัปเดตไม่ได้ (framework-minus-apex, ext และ framework-location) เป้าหมายเหล่านี้จะรวม framework-hiddenapi-proguard-rules (สร้างโดย hiddenapi_to_proguard) เพื่อเก็บสมาชิกที่แสดงในไฟล์ boot/hiddenapi/*.txt ที่ใช้งานอยู่ (ละเว้น hiddenapi-max-target-o.txt)

ก่อนเปิดใช้ obfuscate: true ในแอปพลิเคชัน ให้ตรวจสอบข้อกำหนดเบื้องต้นในการย้ายข้อมูลต่อไปนี้

  • ทรัพยากร Dependency ของ APK ทดสอบภายนอก: หาก APK android_test ภายนอกตั้งค่า instrumentation_for: "MyApp" และเรียกใช้คลาสหรือเมธอดภายในของ MyApp โดยตรง การเปลี่ยนชื่อสัญลักษณ์เหล่านั้นจะทำให้เกิด NoSuchMethodError หรือ NoClassDefFoundError ณ เวลาที่ทำการทดสอบ กำหนดค่า trace_references_from (หรือ allow_traced_references ด้วย trace_references: true) จัดโครงสร้างการทดสอบเป็น APK ที่ใช้เครื่องมือวัดด้วยตนเองซึ่งลิงก์ MyApp.impl แบบคงที่ หรือใส่คำอธิบายประกอบฮุกการทดสอบด้วย @VisibleForTesting (ดูขั้นตอนที่ 2: ย้ายข้อมูลกฎการเก็บรักษาที่ขับเคลื่อนด้วยการทดสอบ) เพื่อให้สัญลักษณ์ภายในที่การทดสอบต้องการได้รับการเก็บรักษาไว้ขณะที่สร้างความสับสนให้กับส่วนที่เหลือของแอป
  • การสะท้อนตามสตริงและ JNI: หากโมดูลค้นหาคลาส เมธอด หรือฟิลด์ตามชื่อสตริงลิเทอรัล (Class.forName, getDeclaredMethod หรือ JNI FindClass และ GetMethodID) โดยไม่มีกฎการเก็บรักษาหรือคำอธิบายประกอบ การสร้างความสับสนจะเปลี่ยนชื่อเป้าหมายเหล่านั้นและทำให้การค้นหารันไทม์ใช้งานไม่ได้ (โปรดทราบว่าการสะท้อนที่ไม่มีคำอธิบายประกอบจะล้มเหลวภายใต้ shrink: true และ optimize: true ด้วย) ใส่คำอธิบายประกอบจุดแรกเข้าเหล่านั้นด้วยคำแนะนำในการเก็บคำอธิบายประกอบ (@UsesReflection, @UsedByReflection หรือ @UsedByNative) เพื่อให้ R8 คงชื่อไว้และสร้างความสับสนให้กับส่วนที่เหลือของโมดูล

ทำตามหลักการไม่มีแฟล็กที่กำหนดเอง

โมดูลแพลตฟอร์ม Android ที่เหมาะสมจะไม่มีไฟล์ proguard.flags หรือ keep.xml ที่กำหนดเอง ในบิลด์แพลตฟอร์ม Android กฎการเก็บที่กำหนดเองส่วนใหญ่จะซ้ำซ้อนกัน ดังนี้

  • เกณฑ์พื้นฐานของแพลตฟอร์มและ AAPT2: จุดแรกเข้าส่วนใหญ่จะได้รับการเก็บรักษาไว้โดยอัตโนมัติอยู่แล้วตามเกณฑ์พื้นฐานของแพลตฟอร์มส่วนกลาง (เช่น @Keep, เมธอด JNI native และ @VisibleForTesting) หรือสร้างโดย AAPT2 จาก AndroidManifest.xml และทรัพยากรเลย์เอาต์ (ดูทำความเข้าใจกฎการเก็บรักษาเริ่มต้นของแพลตฟอร์ม)
  • ทางเลือกที่ตรงเป้าหมาย: ในกรณีที่ต้องเก็บรักษาจุดแรกเข้า ให้เลือกใช้คำอธิบายประกอบของเว็บไซต์ที่ประกาศ (keepanno, @VisibleForTesting), กฎไลบรารีที่ส่งออก (export_proguard_flags_files: true) หรือการลิงก์การทดสอบแบบคงที่แทนไฟล์ .flags ที่แยกต่างหาก (ดูตรวจสอบและย้ายข้อมูลกฎการเก็บรักษาที่มีอยู่)

ก่อนเพิ่มหรือเก็บรักษาไฟล์ proguard.flags หรือ keep.xml ที่กำหนดเอง ให้ตรวจสอบว่ากฎได้รับการจัดการโดยเกณฑ์พื้นฐานเริ่มต้นแล้วหรือสามารถย้ายข้อมูลไปยังคำอธิบายประกอบโค้ดได้หรือไม่

ทำความเข้าใจกฎการเก็บรักษาเริ่มต้นของแพลตฟอร์ม

Soong จะส่งกฎพื้นฐานส่วนกลางต่อไปนี้ไปยังการเรียกใช้ R8 ทุกครั้งในเป้าหมายการคอมไพล์ DEX (android_app, android_test และโมดูล java_library ที่ติดตั้งได้) โดยอัตโนมัติ ซึ่งกำหนดค่าไว้ใน build/soong/java/dex.go ดังนี้

  • build/make/core/proguard.flags: เก็บรักษาคลาสและสมาชิกที่มีคำอธิบายประกอบด้วย @com.android.internal.annotations.VisibleForTesting ในทุกแพ็กเกจ และด้วย @VisibleForTesting (androidx.annotation.VisibleForTesting หรือ com.google.common.annotations.VisibleForTesting) ภายในแพ็กเกจ android.**, com.android.** และ com.google.android.** นอกจากนี้ยังเก็บรักษา@TestApi, @Keep (androidx.annotation, android.support.annotation, com.android.internal.annotations), @KeepForWeakReference, @WeaklyReferencedCallback และ@dalvik.annotation.optimization.**ด้วย
  • build/make/core/proguard_basic_keeps.flags: เก็บแอตทริบิวต์ SourceFile สำหรับสแต็กเทรซ, คำอธิบายประกอบระดับการเข้าถึงรันไทม์ (RuntimeVisible*Annotations), แอตทริบิวต์ Exceptions และ AnnotationDefault, เมธอด native, สมาชิก Serializable, เมธอด @JavascriptInterface, ตัวสร้าง Throwable(String), ฟิลด์ Parcelable$CREATOR และฟิลด์ protobuf MessageLite
  • build/make/core/proguard/kotlin.flags: ปิดเสียงคำเตือนที่ไม่เป็นอันตรายสำหรับคำอธิบายประกอบเมตา Kotlin ที่เฉพาะเจาะจง (kotlin.Metadata และ kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) และตัดคำอธิบายประกอบ Kotlin DebugMetadata ในบิลด์ที่เผยแพร่
  • build/make/core/proguard/checknotnull.flags: แทนที่การเรียกใช้ตัวช่วยตรวจสอบค่า Null ทั่วไป (com.google.common.base.Preconditions.checkNotNull และ dagger.internal.Preconditions.checkNotNull*) ด้วยการตรวจสอบค่า Null ของไบต์โค้ดที่กระชับ ไฟล์นี้ละเว้น Objects.requireNonNull โดยเจตนาเพื่อรักษาข้อความแสดงข้อยกเว้นที่ชัดเจนข้ามขอบเขต API ของเฟรมเวิร์ก
  • build/make/core/proguard/enumvalues.flags: เก็บเมธอด values และ valueOf ไว้ในประเภท enum เว้นแต่จะปิดใช้โดยการกำหนดค่าบิลด์
  • กฎที่ AAPT2 สร้างขึ้นโดยอัตโนมัติ: AAPT2 จะตรวจสอบไฟล์ AndroidManifest.xml, XML เลย์เอาต์ และ XML ค่ากำหนดที่ผสานรวมเพื่อสร้างกฎการเก็บรักษาที่แน่นอนสำหรับ Activity, Service, BroadcastReceiver, ContentProvider, BackupAgent, Application, คลาสย่อย View ที่กำหนดเอง, คลาสย่อย Preference และเมธอด android:onClick ของ XML ที่ลงทะเบียนไว้ทั้งหมด

ตรวจสอบและย้ายข้อมูลกฎการเก็บรักษาที่มีอยู่

เมื่อตรวจสอบไฟล์ proguard.flags หรือ keep.xml ที่มีอยู่ในที่เก็บแพลตฟอร์ม ให้ประเมินกฎแต่ละข้อตามลำดับตามลำดับชั้น 4 ขั้นตอนต่อไปนี้

ขั้นตอนที่ 1: ลบกฎที่ซ้ำซ้อนหรือล้าสมัย

ลบกฎที่ครอบคลุมโดยเกณฑ์พื้นฐานส่วนกลาง, ไฟล์ Manifest ของ AAPT2 และกฎเลย์เอาต์ หรือค่าเริ่มต้นของ R8 รวมถึงกฎที่อ้างอิงคลาสหรือแพ็กเกจที่ไม่มีอยู่แล้ว

หากกฎทั้งหมดในไฟล์ .flags ซ้ำซ้อน ให้ลบไฟล์และนำ proguard_flags_files ออกจาก Android.bp หากบล็อก optimize ที่เหลืออยู่เพียงแค่ระบุการตั้งค่าเริ่มต้นซ้ำ ให้นำบล็อกที่ซ้ำซ้อนออกและจัดรูปแบบไฟล์บิลด์ด้วย bpfmt -w Android.bp เมื่อล้างแพ็กเกจ ให้ตรวจสอบว่าโมดูลที่ใช้ร่วมกันในไดเรกทอรีย่อย (เช่น ตัวแปรเป้าหมาย Kotlin) อ้างอิงไฟล์แฟล็กเดียวกันหรือไม่ และอัปเดตไฟล์เหล่านั้นพร้อมกัน

ขั้นตอนที่ 2: ย้ายข้อมูลกฎการเก็บรักษาที่อิงตามการทดสอบ

ย้ายกฎการเก็บรักษาที่ขับเคลื่อนด้วยการทดสอบออกจากไฟล์ .flags ของเวอร์ชันที่ใช้งานจริงที่กำหนดเองโดยใช้รูปแบบใดรูปแบบหนึ่งต่อไปนี้

  • ติดตามการอ้างอิงจากการทดสอบหรือเป้าหมายที่ใช้ร่วมกัน (trace_references_from หรือ allow_traced_references): เมื่อทั้งเป้าหมายและโมดูลที่ขึ้นต่อกันโดยตรงคอมไพล์ DEX ของตนเอง (android_app, android_test หรือ java_library ที่ใช้ DEX) และเชื่อมต่อผ่าน instrumentation_for หรือ libs Soong จะเรียกใช้ R8 tracereferences ในโมดูลที่ขึ้นต่อกันและป้อนกฎการเก็บที่สร้างขึ้นลงในการคอมไพล์ R8 ของเป้าหมายได้โดยไม่ต้องสร้างวงจรการขึ้นต่อกันหรือต้องแยกไลบรารี .impl ด้วยตนเอง

    • การทดสอบที่เก็บเดียวกัน (trace_references_from): แสดงรายการทรัพยากรที่ขึ้นต่อกันโดยตรงของ instrumentation_for หรือ libs ในโมดูลเป้าหมาย (ซึ่งจะเผยแพร่โดยอัตโนมัติในตัวแปร override_android_app และ override_android_test และยังคงรองรับไลบรารี Java ที่ใช้ร่วมกันซึ่งไม่ขึ้นต่อกันเมื่อจับคู่กับการแยก .impl):

      android_app {
          name: "MyApp",
          srcs: ["src/**/*.java"],
          optimize: {
              obfuscate: true,
              shrink_resources: true,
              trace_references_from: ["MyAppTests"],
          },
      }
      
      android_test {
          name: "MyAppTests",
          instrumentation_for: "MyApp",
          srcs: ["tests/**/*.java"],
      }
      
    • การทดสอบข้ามที่เก็บหรือผู้ให้บริการ (allow_traced_references และ trace_references): เมื่อโมดูลเป้าหมายไม่สามารถแสดงรายการทรัพยากรที่ต้องพึ่งพาใน trace_references_from โดยตรง (เช่น การทดสอบในที่เก็บหรือโครงสร้างผู้ให้บริการอื่น) ให้ตั้งค่า allow_traced_references: true ในโมดูลเป้าหมายและเลือกใช้จากทรัพยากรที่ต้องพึ่งพาแต่ละรายการด้วย trace_references: true:

      android_app {
          name: "MyApp",
          srcs: ["src/**/*.java"],
          optimize: {
              allow_traced_references: true,
          },
      }
      
      android_test {
          name: "MyAppVendorTests",
          instrumentation_for: "MyApp",
          srcs: ["tests/**/*.java"],
          optimize: {
              trace_references: true,
          },
      }
      
    • การสะท้อนตามสตริงในการทดสอบ: tracereferences จะตรวจสอบการอ้างอิงพูลค่าคงที่ของไบต์โค้ดและไม่สามารถตรวจจับการสะท้อนตามสตริงในการทดสอบ (Class.forName, getDeclaredField, getDeclaredMethod หรือตัวช่วยการแทรกฟิลด์แบบสะท้อน) ใส่คำอธิบายประกอบฟิลด์และเมธอดเวอร์ชันที่ใช้งานจริงที่เข้าถึงแบบสะท้อนด้วย @VisibleForTesting (หรือ @UsedByReflection)

  • ลิงก์ไลบรารีการติดตั้งใช้งานเข้ากับการทดสอบ (static_libs): หากคุณไม่ต้องการให้มีจุดแรกเข้าสำหรับการทดสอบเท่านั้นคงอยู่ใน APK เวอร์ชันที่ใช้งานจริง และไม่ต้องการให้การเปลี่ยนแปลงการทดสอบทริกเกอร์ R8 อีกครั้งในเป้าหมายเวอร์ชันที่ใช้งานจริง ให้วางไฟล์ต้นฉบับของแอปไว้ใน android_library (MyApp.impl) และลิงก์ MyApp.impl เข้ากับทั้ง MyApp และ MyAppTests แบบคงที่

    android_library {
        name: "MyApp.impl",
        srcs: ["src/**/*.java"],
        manifest: "AndroidManifest.xml",
    }
    
    android_app {
        name: "MyApp",
        static_libs: ["MyApp.impl"],
        optimize: {
            obfuscate: true,
            shrink_resources: true,
        },
    }
    
    android_test {
        name: "MyAppTests",
        srcs: ["tests/**/*.java"],
        static_libs: ["MyApp.impl"],
    }
    

    รูปแบบ 3 โมดูลนี้ช่วยให้ MyAppTests ทำงานเป็นการทดสอบการวัดคุมด้วยตนเองที่มีสิทธิ์เข้าถึงคลาสภายในอย่างเต็มรูปแบบ ขณะเดียวกันก็ช่วยให้ MyApp ลดขนาดและสร้างความสับสนให้กับจุดแรกเข้าของเวอร์ชันที่ใช้งานจริงเท่านั้น

  • ใส่คำอธิบายประกอบฮุกการทดสอบด้วย @VisibleForTesting: หาก APK ทดสอบภายนอกเรียกใช้เมธอดหรือคอนสตรัคเตอร์ภายในเพียงไม่กี่รายการในเป้าหมาย android_app คุณสามารถใส่คำอธิบายประกอบการประกาศเหล่านั้นด้วย @VisibleForTesting เกณฑ์พื้นฐาน build/make/core/proguard.flags ทั่วโลกจะเก็บรักษา@VisibleForTestingรายการในแพ็กเกจ android.**, com.android.** และ com.google.android.** โดยอัตโนมัติโดยไม่ต้องใช้ไฟล์ .flags ที่กำหนดเอง (สำหรับโมดูลของผู้ให้บริการที่อยู่นอกเนมสเปซเหล่านี้ ให้ใช้ trace_references_from, @UsedByReflection หรือกฎไลบรารีที่ส่งออก) เมื่อใส่คำอธิบายประกอบด้วยตนเองด้วย @VisibleForTesting แทนการใช้ trace_references_from หรือ trace_references ให้ใส่คำอธิบายประกอบคลาสที่ส่งไปยัง Mockito mock(...) หรือ spy(...) ด้วย (ไม่ใช่แค่เมธอดที่ถูก Stub) เนื่องจาก mockito-target (dexmaker-mockito) ในอุปกรณ์จะสร้างคลาสย่อยรันไทม์ และ R8 สามารถทำเครื่องหมายคลาสที่ไม่มีคลาสย่อยเป็น final (MockitoException: Can't mock/spy class ... - final class)

  • หลีกเลี่ยงหรือจัดแพ็กเกจไลบรารีแบบคงที่ที่ทับซ้อนกันใหม่ (jarjar_rules): เมื่อทั้ง android_app และ APK ทดสอบ instrumentation_for ลิงก์ไลบรารีเดียวกันแบบคงที่ (โดยตรง เช่น guava หรือแบบทรานซิทีฟ) tracereferences จะเห็นคลาสเหล่านั้นที่กำหนดไว้ในการทดสอบและไม่แสดงกฎการเก็บรักษาสำหรับแอป และในรันไทม์ ClassLoader ระดับบนของแอปจะบดบังสำเนาของการทดสอบด้วยคลาสที่ลดขนาด R8 ซึ่งทำให้เกิด IllegalAccessError หรือ NoSuchMethodError หากการทดสอบไม่จำเป็นต้องมีสำเนาที่รวมมาด้วย ให้ลบรายการที่ซ้ำกันออกจาก static_libs ของการทดสอบ (หรือลิงก์ผ่าน libs) เพื่อหลีกเลี่ยงการทับซ้อนกัน มิฉะนั้น ให้จัดแพ็กเกจใหม่สำหรับแพ็กเกจไลบรารีแบบคงที่ที่ทับซ้อนกันของแอปภายใต้คำนำหน้าที่เจาะจงโมดูลโดยใช้ jarjar_rules

ขั้นตอนที่ 3: ส่งออกกฎจากคลังที่เป็นเจ้าของ

หาก java_library หรือ android_library ที่แชร์ต้องใช้กฎการเก็บรักษาสำหรับการสะท้อนภายในหรือ Callback ของ JNI ของตนเอง ให้กำหนดกฎในเป้าหมายไลบรารีและตั้งค่า export_proguard_flags_files: true ดังนี้

java_library {
    name: "my-shared-library",
    srcs: ["src/**/*.java"],
    optimize: {
        proguard_flags_files: ["proguard.flags"],
        export_proguard_flags_files: true,
    },
}

เป้าหมาย android_app และไลบรารีปลายน้ำทั้งหมดที่ลิงก์แบบคงที่ my-shared-library จะรับกฎเหล่านั้นโดยอัตโนมัติ ดังนั้นแอปปลายน้ำจึงไม่จำเป็นต้องทำซ้ำ เมื่อหลายโมดูลในไดเรกทอรีต่างๆ ต้องใช้ชุดกฎร่วมกันซึ่งไม่ได้เชื่อมโยงกับไลบรารีโค้ดเดียว ให้เผยแพร่ไฟล์ .flags ผ่าน filegroup ที่ชัดเจนใน Android.bp เพื่อให้โมดูลสามารถอ้างอิง :my-shared-flags ได้อย่างชัดเจนในขอบเขตแพ็กเกจ ดูคำแนะนำทั่วไปเกี่ยวกับ การเขียนกฎการเก็บรักษาของผู้ใช้ไลบรารีโดยไม่จำกัดการเพิ่มประสิทธิภาพแอปขั้นปลายได้ที่การเพิ่มประสิทธิภาพสำหรับผู้เขียนไลบรารี

ขั้นตอนที่ 4: ใส่คำอธิบายประกอบการประกาศในซอร์สโค้ด

เมื่อมีการเข้าถึงคลาส, เมธอด, ตัวสร้าง หรือฟิลด์ผ่านการสะท้อนหรือ JNI และ AAPT2 หรือเกณฑ์พื้นฐานส่วนกลางไม่ครอบคลุม ให้แทนที่กฎ -keep ที่แยกออกมาในไฟล์ .flags ด้วยคำอธิบายประกอบแหล่งที่มาจากคำแนะนำในการเก็บคำอธิบายประกอบ (ดูข้อมูลอ้างอิง keepanno Javadoc)

  • @UsesReflection: แนะนำให้ใช้คำอธิบายประกอบนี้เมื่อคุณควบคุมโค้ดที่ทำการรีเฟล็กชัน วางไว้ที่ตำแหน่งการเรียกใช้การสะท้อนเพื่อประกาศคลาส เมธอด หรือฟิลด์เป้าหมายที่มีการเข้าถึงแบบไดนามิก เนื่องจาก @UsesReflection เข้ารหัสเงื่อนไขเบื้องต้นโดยอัตโนมัติว่าเว็บไซต์การโทรที่มีคำอธิบายประกอบนั้นเข้าถึงได้ R8 จึงตัดทั้งผู้โทรและเป้าหมายที่สะท้อนหากไม่ได้ใช้เว็บไซต์การโทร
  • @UsedByReflection: วางในคลาส เมธอด ฟิลด์ หรือคอนสตรัคเตอร์ที่สร้างอินสแตนซ์หรือเรียกใช้แบบสะท้อนโดยโค้ดหรือไลบรารีภายนอก เช่น คลาสที่โหลดด้วย Class.forName จากคีย์ Bundle หรือ Settings หรือคลาสปลั๊กอินที่โหลดใน ClassLoader แบบไดนามิก ระบุ kind (เช่น KeepItemKind.CLASS_AND_METHODS), preconditions (@KeepCondition) และข้อจำกัดของพารามิเตอร์เพื่อให้ R8 เก็บเฉพาะสัญญาการสะท้อนที่ตรงกันทุกประการ และยังคงเพิ่มประสิทธิภาพหรือตัดสมาชิกที่ไม่ได้ใช้ของคลาสออกได้ อย่าเพิ่ม @UsedByReflection ลงในคอมโพเนนต์ที่ลงทะเบียนในไฟล์ Manifest เช่น JobService หรือ BroadcastReceiver เนื่องจาก AAPT2 เก็บไว้อยู่แล้ว
  • @UsedByNative: วางบนเมธอดหรือฟิลด์ที่เข้าถึงจากโค้ด C หรือ C++ JNI โดยใช้ GetMethodID, GetStaticMethodID หรือ GetFieldID
  • @KeepForApi: วางบนคลาสหรือสมาชิก API ของไลบรารีที่ต้องคงสภาพเดิมเมื่อไลบรารีถูกย่อขนาดก่อนการเผยแพร่
  • @Keep (androidx.annotation.Keep): ใช้เป็นตัวเลือกสำรองเมื่อใช้ keepanno ไม่ได้ การใช้ @Keep กับชั้นเรียนจะเก็บรักษาชั้นเรียนและสมาชิกทั้งหมดไว้โดยไม่มีเงื่อนไข การใช้ @Keep กับเมธอดหรือฟิลด์จะทำหน้าที่เป็นจุดแรกเข้าแบบไม่มีเงื่อนไขซึ่งจะเก็บสมาชิกและคลาสที่มีสมาชิกนั้นไว้แม้ว่าจะไม่มีการสร้างอินสแตนซ์ของคลาสก็ตาม ส่วน keepanno จะแสดงความสามารถในการเข้าถึงแบบมีเงื่อนไข

วิธีใช้คำอธิบายประกอบ R8 keepanno ในโมดูล Soong

  1. เพิ่ม "keepanno-annotations" ไปยัง libs ใน Android.bp:

    libs: [
        "keepanno-annotations",
    ],
    
  2. นำเข้า com.android.tools.r8.keepanno.annotations.* และใส่คำอธิบายประกอบการประกาศหรือเว็บไซต์การโทรในซอร์สโค้ด Java หรือ Kotlin ดังนี้

    import com.android.tools.r8.keepanno.annotations.KeepItemKind;
    import com.android.tools.r8.keepanno.annotations.UsedByReflection;
    
    public final class CustomPluginController {
        @UsedByReflection(
            description = "Instantiated via Class.forName from plugin config",
            kind = KeepItemKind.CLASS_AND_METHODS)
        public CustomPluginController(Context context) {
            // ...
        }
    }
    

    R8 จะแปลคำอธิบายประกอบ keepanno เป็นโมเดลกฎการเก็บรักษาภายในโดยตรง และตัดคำอธิบายประกอบออกจากเอาต์พุต DEX สุดท้าย โดยไม่เพิ่มค่าใช้จ่ายเพิ่มเติมสำหรับไบต์โค้ดรันไทม์

หลีกเลี่ยงข้อผิดพลาดที่พบบ่อย

ส่วนต่อไปนี้จะอธิบายข้อผิดพลาดที่พบบ่อยเกี่ยวกับ proguard.flags และ keep.xml ในโค้ดแพลตฟอร์มและวิธีแก้ไข

กฎการเก็บรักษาองค์ประกอบแบบกว้าง

# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
  • สาเหตุที่ส่งผลเสียต่อประสิทธิภาพ: กฎนี้ซ้ำซ้อนกับ AAPT2 โดยสิ้นเชิง เนื่องจากใช้ไวลด์การ์ดโดยไม่ตรวจสอบว่าคอมโพเนนต์ได้รับการลงทะเบียนใน AndroidManifest.xml ที่ผสานขั้นสุดท้ายหรือไม่ จึงบังคับให้ R8 เก็บซับคลาส Activity, Service หรือ BroadcastReceiver ทุกรายการที่พบใน classpath รวมถึงคอมโพเนนต์ไลบรารีที่ไม่ได้ใช้และกิจกรรมการแก้ไขข้อบกพร่องที่ปิดใช้
  • วิธีแก้ไขที่แนะนำ: ลบกฎ AAPT2 จะตรวจสอบไฟล์ Manifest ที่ผสานแล้วและสร้างกฎ -keep ที่แน่นอนสำหรับคอมโพเนนต์ที่ลงทะเบียนไว้

กฎการเก็บรักษาตัวแฮนเดิลการคลิก XML แบบกว้าง

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • สาเหตุที่ทำให้ประสิทธิภาพลดลง: การเก็บรักษาเมธอด public void *(View) ทุกเมธอดในทุกคลาสในโมดูลจะป้องกันไม่ให้ R8 ตัดหรือแทรกเมธอดที่ยอมรับพารามิเตอร์ View รายการเดียว
  • วิธีแก้ไขที่แนะนำ: ลบกฎ AAPT2 จะสแกนไฟล์ XML ของเลย์เอาต์และสร้างกฎการเก็บที่กำหนดเป้าหมายสำหรับเมธอดที่แอตทริบิวต์ android:onClick อ้างอิง

กฎการเก็บรักษา Getter และ Setter ของ Broad View

# Don't do this:
-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
    public *** get*();
}
  • เหตุใดจึงส่งผลเสียต่อประสิทธิภาพ: กฎนี้บังคับให้ R8 เก็บ Getter และ Setter ทุกรายการในคลาสย่อย View ทุกคลาสในแอปและไลบรารีที่ลิงก์ทั้งหมด (รวมถึงไลบรารี AndroidX และ Material) ซึ่งจะบล็อกการนำเมธอดที่ไม่ได้ใช้งานออกและการแทรกในโค้ด UI
  • วิธีแก้ไขที่แนะนำ: ลบกฎ AAPT2 จะเก็บรักษาคอนสตรัคเตอร์สำหรับคลาส View ที่กำหนดเองซึ่งขยายจากไฟล์ XML ของเลย์เอาต์อยู่แล้ว หากโค้ดทำให้พร็อพเพอร์ตี้มุมมองเคลื่อนไหวโดยใช้ชื่อสตริงแบบสะท้อน เช่น ObjectAnimator.ofFloat(view, "translationZ", ...) ให้แทนที่ชื่อสตริงด้วยการอ้างอิงพร็อพเพอร์ตี้ที่พิมพ์ (View.TRANSLATION_Z หรือการติดตั้งใช้งาน FloatProperty หรือ IntProperty ที่กำหนดเอง) เพื่อหลีกเลี่ยงการสะท้อนโดยสิ้นเชิง หากหลีกเลี่ยงการเข้าถึงพร็อพเพอร์ตี้แบบสะท้อนไม่ได้ ให้ใส่คำอธิบายประกอบเมธอด Getter หรือ Setter ที่เฉพาะเจาะจงด้วย @UsedByReflection

การเพิ่มประสิทธิภาพแบบครอบคลุมหรือการปิดใช้การสร้างความสับสน

# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
  • เหตุใดจึงส่งผลเสียต่อประสิทธิภาพ: การวาง -dontoptimize หรือ -dontshrink ไว้ในไฟล์ .flags จะลบล้างการตั้งค่า Android.bp ของโมดูลโดยไม่แจ้งให้ทราบ และปิดใช้การเพิ่มประสิทธิภาพในเป้าหมายทั้งหมด ยิ่งไปกว่านั้น หากไลบรารีส่งออกไฟล์ .flags ที่มี -dontoptimize ระบบจะปิดใช้การเพิ่มประสิทธิภาพ R8 สำหรับ android_app ดาวน์สตรีมทุกรายการที่ลิงก์ไลบรารี
  • การแก้ไขที่แนะนำ: นำ -dontoptimize, -dontshrink และ -dontobfuscate ออกจากไฟล์ .flags รายการ ควบคุมลักษณะการเพิ่มประสิทธิภาพอย่างชัดเจนใน Android.bp โดยใช้บล็อก optimize (shrink, optimize, obfuscate) ในเป้าหมาย Leaf

การแจ้งว่าไม่เหมาะสมเพื่อการวินิจฉัยในกฎที่คอมมิต

# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
  • เหตุใดจึงส่งผลเสียต่อบิลด์: แฟล็กการวินิจฉัยจะทำให้บันทึกบิลด์เต็มหรือพยายามเขียนไฟล์เอาต์พุตไปยังเส้นทางในเครื่องระหว่างบิลด์ Soong ที่ทำในแซนด์บ็อกซ์
  • วิธีแก้ไขที่แนะนำ: ลบแฟล็กเหล่านี้ออกจากไฟล์ .flags ที่คอมมิตแล้ว Soong จะเขียนเอาต์พุตการแมปและการใช้งาน R8 เช่น proguard_dictionary และ proguard_usage.zip ไปยังไดเรกทอรีกลางของโมดูลใน out/soong/.intermediates/ โดยอัตโนมัติ

กฎการเก็บรักษาเวอร์ชันที่ใช้งานจริงสำหรับโค้ดทดสอบ

  • เหตุใดจึงส่งผลเสียต่อประสิทธิภาพ: การเพิ่มกฎ -keep ที่กำหนดเองเพื่อการเข้าถึงการทดสอบเพียงอย่างเดียวจะบังคับให้โค้ดนั้นยังคงไม่ถูกสร้างความสับสนและเก็บไว้ในบิลด์เวอร์ชันที่ใช้งานจริง แม้ว่าการกำหนดค่า trace_references_from (หรือ allow_traced_references ด้วย trace_references: true) หรือการใส่คำอธิบายประกอบเมธอดด้วย @VisibleForTesting จะช่วยหลีกเลี่ยงการคงกฎ -keep ด้วยตนเองและเก็บเฉพาะสิ่งที่การทดสอบอ้างอิงไว้ แต่สัญลักษณ์เหล่านั้นจะยังคงอยู่ในไบนารีเวอร์ชันที่ใช้งานจริง
  • การแก้ไขที่แนะนำ: กำจัดกฎ -keep ของการทดสอบที่กำหนดเองโดยเปิดใช้ trace_references_from (หรือ allow_traced_references และ trace_references: true) หรือจัดโครงสร้างแอปเป็นไลบรารี .impl ที่ลิงก์กับ APK ทดสอบที่วัดคุมด้วยตนเองโดยใช้ static_libs เพื่อให้จุดแรกเข้าสำหรับการทดสอบเท่านั้นอยู่นอก APK เวอร์ชันที่ใช้งานจริงโดยสมบูรณ์ ตามที่อธิบายไว้ในขั้นตอนที่ 2: ย้ายข้อมูลกฎการเก็บรักษาที่ขับเคลื่อนด้วยการทดสอบ

ไวลด์การ์ดทรัพยากรแบบครอบคลุมใน keep.xml

<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@raw/*,@drawable/*,@string/*" />
  • เหตุผลที่ส่งผลเสียต่อประสิทธิภาพ: ไวลด์การ์ดแบบครอบคลุมจะเก็บทรัพยากรทุกรายการในประเภทที่ตรงกันทั่วทั้งโมดูลและการขึ้นต่อกัน ซึ่งจะขัดขวางการลดขนาดทรัพยากร (shrink_resources: true) และทำให้ APK และตาราง resources.arsc ที่แมปไว้มีขนาดใหญ่ขึ้น
  • วิธีแก้ไขที่แนะนำ
    • ใช้การอ้างอิงทรัพยากรแบบคงที่แทน keep.xml: หลีกเลี่ยงการใช้เมธอด Resources.getIdentifier เพื่อค้นหาชุดทรัพยากรที่จำกัดแบบไดนามิก เช่น สตริงการทดสอบที่มีหมายเลขหรือ Drawable ที่มีธีม แต่ให้ใช้คำสั่ง switch เวลาคอมไพล์หรือแมปค่าคงที่ R.id, R.string หรือ R.drawable แบบคงที่แทน การอ้างอิงแบบคงที่ช่วยให้ R8 และ AAPT2 ติดตามทรัพยากรที่ใช้งานอยู่ได้อย่างถูกต้อง กำจัดค่าใช้จ่ายเพิ่มเติมในการค้นหาสตริงรันไทม์ และไม่จำเป็นต้องใช้ keep.xml เลย
    • ระบุชื่อทรัพยากรที่เฉพาะเจาะจง: หากจำเป็นต้องใช้การค้นหาแบบไดนามิก เช่น โดยโปรแกรมอ่านใบอนุญาตของบุคคลที่สาม ให้ระบุตัวระบุทรัพยากรที่แน่นอนใน tools:keep (เช่น tools:keep="@raw/third_party_licenses")
    • ลบไฟล์ keep.xml ที่ซ้ำซ้อน: หากทรัพยากรที่แสดงมีการอ้างอิงแบบคงที่ในโค้ด (R.raw.foo) หรือ XML (@raw/foo) อยู่แล้ว เครื่องมือลดขนาดจะเก็บทรัพยากรเหล่านั้นไว้โดยอัตโนมัติ คุณจึงลบ res/raw/keep.xml ได้

ยืนยันและตรวจสอบการเปลี่ยนแปลงกฎ

เมื่อใดก็ตามที่คุณนำกฎการเก็บรักษาออกหรือจำกัดกฎการเก็บรักษาใน proguard.flags หรือ keep.xml ให้ตรวจสอบว่าโมดูลสร้างขึ้นอย่างสมบูรณ์ ผ่านการทดสอบหน่วยและการทดสอบการวัดคุม และคงจุดแรกเข้าที่จำเป็นทั้งหมดไว้

สร้างและทดสอบโมดูล

  1. สร้างโมดูลใหม่ทั้งหมดเพื่อยืนยันว่า R8 และ AAPT2 ทำงานเสร็จสมบูรณ์โดยไม่มีคำเตือนเกี่ยวกับการอ้างอิงที่ขาดหายไป

    m <MODULE_NAME>
    
  2. เรียกใช้การทดสอบหน่วยและการวัดคุมสำหรับโมดูลโดยใช้ atest:

    atest <TEST_MODULE_NAME>
    
  3. สำหรับแอประบบ บริการที่มีสิทธิ์ หรือโมดูลที่ขึ้นอยู่กับฮาร์ดแวร์ ให้เรียกใช้การทดสอบการวัดคุมและ UI ในอุปกรณ์จริงเป้าหมายหรือในห้องปฏิบัติการทดสอบอุปกรณ์เพื่อทดสอบเส้นทางการสะท้อนรันไทม์ การเชื่อมโยง IPC และการขยายทรัพยากร

ตรวจสอบ DEX และความแตกต่างของทรัพยากร

เปรียบเทียบ APK หรือ JAR ที่คอมไพล์แล้วก่อนและหลังการเปลี่ยนแปลงกฎเพื่อยืนยันว่า R8 นำโค้ดและทรัพยากรที่ไม่ได้ใช้แล้วออกโดยไม่ตัดจุดแรกเข้าที่คาดไว้ออก

  • ใช้ apkanalyzer หรือ dexdump เพื่อตรวจสอบคลาส เมธอด และฟิลด์ที่เก็บไว้ในไฟล์ APK หรือ DEX เอาต์พุต

    apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • เมื่อแก้ไข keep.xml หรือเปิดใช้ shrink_resources ให้ใช้ aapt2 dump resources เพื่อยืนยันว่าบิลด์จะตัดทรัพยากรที่ไม่ได้ใช้ออกและเก็บทรัพยากรที่จำเป็นไว้ ดังนี้

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • เมื่อเรียกใช้ prebuilts/r8/r8.jar โดยตรงใน classes.jar ก่อน R8 ของโมดูลเพื่อยืนยันการเปลี่ยนแปลงแฟล็กแบบออฟไลน์โดยไม่ต้องสร้างแพลตฟอร์มแบบเต็ม ให้ส่งทั้ง JAR คลาสพาธ --lib ของโมดูล (android.jar, framework.jar และ libs ที่แชร์) และกฎ proguard.options ที่สร้างขึ้นของ AAPT2 จาก AndroidManifest.xml และ XML เลย์เอาต์ หากไม่มีอินพุตเหล่านั้น R8 จะไม่สามารถแก้ไขซูเปอร์ไทป์ของเฟรมเวิร์ก (Activity, Service, Phone, Handler) และเก็บรักษาเมธอดลำดับชั้นไว้ไม่เพียงพอ

วิเคราะห์รัศมีการเก็บรักษาและการรวมกฎด้วย R8

คอมไพเลอร์ R8 แบบโอเพนซอร์สมีเครื่องมือวิเคราะห์ Keep Radius (ซึ่งแสดงใน Android Studio และ Gradle เป็นเครื่องมือวิเคราะห์การกำหนดค่า R8 ด้วย) ที่วัดผลกระทบที่แน่นอนของกฎการเก็บรักษาทุกข้อในระหว่างการคอมไพล์ Soong จะผสานรวมเครื่องมือวิเคราะห์นี้เข้ากับบิลด์แพลตฟอร์ม Android โดยตรง (กำหนดค่าใน build/soong/java/dex.go)

วิธีวิเคราะห์กฎการเก็บรักษาสำหรับโมดูลแพลตฟอร์มหรือทั้งบิลด์มีดังนี้

  1. เรียกใช้บิลด์ด้วยตัวแปรสภาพแวดล้อม R8_DUMP_KEEP_RADIUS=true ดังนี้

    R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>
    

    เมื่อตั้งค่า R8_DUMP_KEEP_RADIUS=true แล้ว Soong จะสั่งให้ R8 บันทึกเมตริกกฎการเก็บไว้ในไฟล์ r8keepradius.pb ระดับกลางสำหรับแต่ละโมดูลที่คอมไพล์ภายใต้ out/soong/.intermediates/ สำหรับกฎการเก็บรักษาและคำอธิบายประกอบ keepanno แต่ละรายการ R8 จะบันทึกข้อมูลต่อไปนี้

    • รัศมีการเก็บรักษาทันที: คลาส ฟิลด์ และเมธอดที่แน่นอนซึ่งเก็บรักษาไว้โดยกฎ พร้อมกับข้อจำกัดเฉพาะที่บังคับใช้กับการลดขนาด การเพิ่มประสิทธิภาพ หรือการสร้างความสับสน
    • การรวมกฎ: กฎการเก็บรักษาหรือคำอธิบายประกอบอื่นๆ ที่เก็บรายการเดียวกันไว้แล้ว หากกฎที่กำหนดเองถูกรวมไว้ในกฎ AAPT2 หรือกฎพื้นฐานส่วนกลางอย่างสมบูรณ์ คุณจะลบกฎดังกล่าวได้อย่างปลอดภัย
    • กฎทั่วทั้งแพ็กเกจและกฎส่วนกลาง: กฎที่ใช้ไวลด์การ์ดทั่วทั้งแพ็กเกจแบบกว้างหรือใช้คำสั่งการกำหนดค่าส่วนกลาง
  2. แปลงเอาต์พุต r8keepradius.pb เป็นรายงาน HTML แบบอินเทอร์แอกทีฟโดยใช้ KeepRadiusHtmlReportGenerator (รวมอยู่ใน prebuilts/r8/r8.jar) ดังนี้

    # Generate an HTML report for a single module
    INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME>
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        $INTERMEDIATES/android_common/r8keepradius.pb \
        keep_radius_report.html
    
    # Scan all built modules under out/soong/.intermediates and generate
    # per-module HTML reports plus an aggregate keepradius.html summary
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        out/soong/.intermediates \
        out/keep_radius_reports
    

    เมื่อได้รับไดเรกทอรี KeepRadiusHtmlReportGenerator จะตรวจสอบไฟล์ *keepradius*.pb ทั้งหมด สร้างรายงาน HTML สำหรับแต่ละโมดูล และสร้าง out/keep_radius_reports/keepradius.html ที่สรุปจำนวนรายการที่ใช้งานอยู่ จำนวนรายการที่เก็บไว้ และกฎการเก็บที่มีรัศมีสูงสุดทั่วทั้งบิลด์ ดูข้อมูลเพิ่มเติมเกี่ยวกับการตีความคะแนนการลดขนาด การเพิ่มประสิทธิภาพ และการสร้างความสับสนในรายงานที่สร้างขึ้นได้ที่หัวข้อใช้เครื่องมือวิเคราะห์การกำหนดค่า R8

นอกจากนี้ Soong ยังรองรับตัวแปรสภาพแวดล้อมการวินิจฉัย R8 และ Inner-loop เพิ่มเติมใน build/soong/java/dex.go ดังนี้

  • R8_DUMP_INPUT=true: เขียน r8inputs.zip ไปยังไดเรกทอรีระดับกลางของโมดูลที่มี JAR อินพุต, JAR ไลบรารี และการกำหนดค่า ProGuard ที่ผสานรวมทั้งหมดสำหรับการจำลอง R8 แบบสแตนด์อโลน
  • R8_DUMP_PERFETTO_TRACE=true: เขียน r8trace.ptrace ไปยังไดเรกทอรีระดับกลางของโมดูลเพื่อตรวจสอบการคอมไพล์ R8 ที่ผ่านใน Perfetto
  • SOONG_SKIP_TRACE_REFERENCES=true: ข้ามการเรียกใช้กฎ tracereferences ที่กำหนดค่าโดย trace_references_from หรือ trace_references เพื่อเร่งความเร็วของบิลด์ Inner Loop ภายในเครื่องเมื่อทำซ้ำในโค้ดทดสอบ

ตรวจสอบกฎผู้ใช้ไลบรารี

เมื่อ java_library หรือ android_library ส่งออกกฎการเก็บรักษาไปยังแอปผู้บริโภคปลายน้ำ (export_proguard_flags_files: true) กฎเหล่านั้นต้องไม่มีแฟล็กส่วนกลางที่แก้ไขหรือปิดใช้การเพิ่มประสิทธิภาพสำหรับแอปที่ใช้

R8 มีเครื่องมือวิเคราะห์กฎการเก็บรักษาแบบโอเพนซอร์ส (ProcessKeepRules) ซึ่งแสดงในบิลด์แพลตฟอร์ม Android เป็นเครื่องมือโฮสต์ process-keep-rules (กำหนดไว้ใน prebuilts/r8/Android.bp) ดังนี้

# Build the R8 keep rules validator host binary
m process-keep-rules

# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>

เครื่องมือ process-keep-rules จะแยกวิเคราะห์ไฟล์การกำหนดค่าแต่ละไฟล์และดำเนินการไม่สำเร็จพร้อมกับการวินิจฉัยไฟล์และบรรทัดหากพบคำสั่งที่ไม่อนุญาตในกฎของผู้ใช้ไลบรารี คำสั่งที่ไม่อนุญาต ได้แก่ การเพิ่มประสิทธิภาพทั่วโลก การย่อขนาด หรือการปิดใช้การสร้างความสับสน (เช่น -dontoptimize หรือ -dontshrink) แฟล็กการจัดแพ็กเกจใหม่และการแก้ไขการเข้าถึง แฟล็กการวินิจฉัยหรือการแมป และ -keepattributes ระดับแอป

ตรวจสอบโครงสร้างแหล่งที่มาด้วย pgaudit.py

หากต้องการตรวจสอบไดเรกทอรีต้นทางที่ยังไม่ได้สร้างควบคู่ไปกับเครื่องมือวิเคราะห์เวลาบิลด์ของ R8 แผนผังแพลตฟอร์ม Android จะมีสคริปต์ pgaudit.py อยู่ใน build/make/core/proguard/tools/ เครื่องมือวิเคราะห์แบบคงที่นี้จะสแกนไฟล์ Android.bp, proguard.flags และ keep.xml ในที่เก็บเพื่อแจ้งกฎที่ครอบคลุมโดย AAPT2 หรือเกณฑ์พื้นฐานของแพลตฟอร์มส่วนกลาง, ไวลด์การ์ดแบบกว้าง, การลบล้าง -dontoptimize และกฎการเก็บรักษาเฉพาะการทดสอบ ดังนี้

# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/

# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json

ทีมแพลตฟอร์มและ OEM สามารถรวม pgaudit.py เพื่อการคัดแยกแผนผังซอร์สโค้ดอย่างรวดเร็ว process-keep-rules เพื่อตรวจสอบกฎไลบรารีที่ส่งออก และ R8_DUMP_KEEP_RADIUS=true กับ KeepRadiusHtmlReportGenerator เพื่อวัดรัศมีการเก็บรักษาคลาส ฟิลด์ และเมธอดที่แน่นอนของทุกกฎในบิลด์