ข่าวสารผลิตภัณฑ์

กำหนดค่าและแก้ปัญหาเกี่ยวกับกฎการเก็บรักษาของ R8

ใช้เวลาอ่าน 7 นาที
Ajesh Pai & Ben Weiss

ในการพัฒนา Android สมัยใหม่ การจัดส่งแอปพลิเคชันขนาดเล็ก รวดเร็ว และปลอดภัยเป็นความคาดหวังพื้นฐานของผู้ใช้ เครื่องมือหลักของระบบบิลด์ Android ที่ใช้ในการดำเนินการนี้คือเครื่องมือเพิ่มประสิทธิภาพ R8  ซึ่งเป็นคอมไพเลอร์ที่จัดการโค้ดและทรัพยากรที่ไม่ได้ใช้งานเพื่อการลดขนาด การเปลี่ยนชื่อโค้ดหรือการลดขนาด และการเพิ่มประสิทธิภาพแอป

การเปิดใช้ R8 เป็นขั้นตอนสำคัญในการเตรียมแอปสำหรับการเผยแพร่ แต่ต้องให้นักพัฒนาแอปให้คำแนะนำในรูปแบบของ "กฎการเก็บ"

หลังจากอ่านบทความนี้แล้ว โปรดดูวิดีโอ "สัปดาห์ไฮไลต์ด้านประสิทธิภาพ" เกี่ยวกับการเปิดใช้ การแก้ไขข้อบกพร่อง และการแก้ปัญหาเครื่องมือเพิ่มประสิทธิภาพ R8 บน YouTube

 

 

เหตุผลที่ต้องมีกฎการเก็บรักษา

ความจำเป็นในการเขียนกฎ Keep เกิดจากความขัดแย้งหลักที่ว่า R8 เป็นเครื่องมือวิเคราะห์แบบคงที่ แต่แอป Android มักจะอาศัยรูปแบบการดำเนินการแบบไดนามิก เช่น การสะท้อนหรือการเรียกเข้าและออกจากโค้ดเนทีฟโดยใช้ JNI (Java Native Interface)

R8 สร้างกราฟของโค้ดที่ใช้โดยการวิเคราะห์การเรียกโดยตรง เมื่อเข้าถึงโค้ดแบบไดนามิก การวิเคราะห์แบบคงที่ของ R8 จะคาดการณ์ไม่ได้ และจะระบุโค้ดนั้นเป็นไม่ได้ใช้และนำออก ซึ่งจะทําให้เกิดข้อขัดข้องขณะรันไทม์

กฎ Keep คือคำสั่งที่ชัดเจนสำหรับคอมไพเลอร์ R8 ซึ่งระบุว่า "คลาส เมธอด หรือฟิลด์ที่เฉพาะเจาะจงนี้เป็นจุดแรกเข้าที่จะเข้าถึงแบบไดนามิกในรันไทม์ คุณต้องเก็บไว้แม้ว่าจะไม่พบการอ้างอิงโดยตรงก็ตาม"

ดูรายละเอียดเพิ่มเติมเกี่ยวกับกฎการเก็บรักษาได้ในคำแนะนำอย่างเป็นทางการ

ที่ที่จะเขียนกฎของ Keep

กฎการเก็บรักษาที่กำหนดเองสำหรับแอปพลิเคชันจะเขียนในไฟล์ข้อความ ตามธรรมเนียมแล้ว ไฟล์นี้จะมีชื่อว่า proguard-rules.pro และอยู่ในรูทของแอปหรือโมดูลไลบรารี จากนั้นระบุไฟล์นี้ในประเภทบิลด์ release ของไฟล์ build.gradle.kts ของโมดูล

release {

    isShrinkResources = true

    isMinifyEnabled = true

    proguardFiles(

        getDefaultProguardFile("proguard-android-optimize.txt"),

        "proguard-rules.pro",

    )

}

ใช้ไฟล์เริ่มต้นที่ถูกต้อง

getDefaultProguardFile เมธอดจะนำเข้าชุดกฎเริ่มต้นที่ Android SDK จัดเตรียมไว้ การใช้ไฟล์ที่ไม่ถูกต้องอาจทำให้แอปไม่ได้รับการเพิ่มประสิทธิภาพ โปรดใช้ proguard-android-optimize.txt ไฟล์นี้มีกฎการเก็บเริ่มต้นสำหรับคอมโพเนนต์ Android มาตรฐาน และ เปิดใช้การเพิ่มประสิทธิภาพโค้ดของ R8 proguard-android.txtที่ล้าสมัยจะให้เฉพาะกฎ Keep แต่ไม่ได้เปิดใช้การเพิ่มประสิทธิภาพของ R8

progaurd.png

เนื่องจากปัญหานี้ส่งผลต่อประสิทธิภาพอย่างร้ายแรง เราจึงเริ่มเตือนนักพัฒนาแอปเกี่ยวกับการใช้ไฟล์ที่ไม่ถูกต้องตั้งแต่ฟีเจอร์ใหม่ของ Android Studio Narwhal 3 และตั้งแต่ปลั๊กอิน Android Gradle เวอร์ชัน 9.0 เป็นต้นไป เราจะไม่รองรับไฟล์ proguard-android.txt ที่ล้าสมัยอีกต่อไป ดังนั้นโปรดอัปเกรดเป็นเวอร์ชันที่เพิ่มประสิทธิภาพแล้ว

วิธีเขียนกฎของ Keep

กฎการเก็บรักษาประกอบด้วย 3 ส่วนหลักดังนี้

  1. ตัวเลือก  เช่น -keep หรือ -keepclassmembers
  2. ตัวแก้ไขที่ไม่บังคับ เช่น allowshrinking
  3. ข้อกำหนดของคลาสที่กำหนดโค้ดที่จะจับคู่

ดูไวยากรณ์และตัวอย่างทั้งหมดได้ที่คำแนะนำในการเพิ่มกฎของ Keep

รูปแบบการต่อต้านกฎของ Keep

คุณควรรู้จักแนวทางปฏิบัติแนะนำและรูปแบบที่ไม่ควรทำ รูปแบบที่ไม่ดีเหล่านี้มักเกิดจากการเข้าใจผิดหรือการลัดขั้นตอนในการแก้ปัญหา และอาจส่งผลร้ายต่อประสิทธิภาพของบิลด์เวอร์ชันที่ใช้งานจริง

ตัวเลือกทั่วโลก

โดยการติดธงเหล่านี้เป็นการเปิด/ปิดทั่วโลกที่ไม่ควรใช้ในบิลด์ที่เผยแพร่โดยเด็ดขาด โดยใช้สำหรับการแก้ไขข้อบกพร่องชั่วคราวเพื่อแยกปัญหาเท่านั้น

การใช้ -dontotptimize จะปิดใช้การเพิ่มประสิทธิภาพของ R8 อย่างมีประสิทธิภาพ ซึ่งจะส่งผลให้แอปทำงานช้าลง

เมื่อใช้ -dontobfuscate คุณจะปิดใช้การเปลี่ยนชื่อทั้งหมด และการใช้ -dontshrink จะปิดการนำโค้ดที่ไม่ได้ใช้ออก กฎส่วนกลางทั้ง 2 ข้อนี้จะเพิ่มขนาดแอป

หลีกเลี่ยงการใช้แฟล็กส่วนกลางเหล่านี้ในสภาพแวดล้อมฮาร์ดแวร์และซอฟต์แวร์ทุกครั้งที่เป็นไปได้เพื่อให้ผู้ใช้แอปได้รับประสบการณ์ที่มีประสิทธิภาพมากขึ้น

กฎการเก็บที่กว้างเกินไป

วิธีที่ง่ายที่สุดในการทำให้ประโยชน์ของ R8 เป็นโมฆะคือการเขียนกฎการเก็บรักษาที่กว้างเกินไป กฎเช่นกฎด้านล่างจะสั่งให้เครื่องมือเพิ่มประสิทธิภาพ R8 ไม่ย่อ ไม่ทำให้สับสน และไม่เพิ่มประสิทธิภาพคลาสใดๆ ในแพ็กเกจนี้หรือแพ็กเกจย่อยใดๆ ของแพ็กเกจนี้ ซึ่งจะลบประโยชน์ของ R8 สำหรับทั้งแพ็กเกจนั้นออกโดยสมบูรณ์ ลองเขียนกฎของ Keep ที่เจาะจงและเฉพาะเจาะจงแทน
 

-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMS

โอเปอเรเตอร์การกลับค่า (!)

ตัวดำเนินการผกผัน (!) ดูเหมือนจะเป็นวิธีที่มีประสิทธิภาพในการยกเว้นแพ็กเกจจากกฎ แต่เรื่องนี้ไม่ได้ง่ายขนาดนั้น ลองดูตัวอย่างนี้

-keep class !com.example.my_package.** { *; } // USE WITH CAUTION

คุณอาจคิดว่ากฎนี้หมายความว่า "อย่าเก็บคลาสไว้ในcom.example.package" แต่จริงๆ แล้วหมายความว่า "เก็บทุกคลาส เมธอด และพร็อพเพอร์ตี้ ในทั้งแอปพลิเคชันที่ไม่ได้อยู่ใน com.example.package" หากคุณรู้สึกแปลกใจกับเรื่องนี้ โปรดตรวจสอบการปฏิเสธในการกำหนดค่า R8

กฎที่ซ้ำกันสำหรับคอมโพเนนต์ Android

ข้อผิดพลาดที่พบบ่อยอีกอย่างคือการเพิ่มกฎ Keep สำหรับ Activities, Services หรือ BroadcastReceivers ของแอปด้วยตนเอง ซึ่งไม่จำเป็น proguard-android-optimize.txtเริ่มต้นมีกฎที่เกี่ยวข้องสำหรับคอมโพเนนต์ Android มาตรฐานเหล่านี้อยู่แล้วเพื่อให้ทำงานได้ทันที

นอกจากนี้ ห้องสมุดหลายแห่งยังมีกฎของ Keep เป็นของตนเองด้วย ดังนั้นคุณจึงไม่จำเป็นต้องเขียนกฎของคุณเองสำหรับรายการเหล่านี้ ในกรณีที่กฎ Keep จากไลบรารีที่คุณใช้มีปัญหา ขอแนะนำให้ติดต่อผู้เขียนไลบรารีเพื่อดูว่าปัญหาเกิดจากอะไร

แนวทางปฏิบัติแนะนำในการใช้กฎ

ตอนนี้คุณรู้แล้วว่าไม่ควรทำอะไรบ้าง เรามาพูดถึงแนวทางปฏิบัติแนะนำกัน

เขียนกฎการเก็บรักษาที่เฉพาะเจาะจง

กฎการเก็บรักษาที่ดีควรแคบและเฉพาะเจาะจงที่สุดเท่าที่จะเป็นไปได้ โดยควรรักษาสิ่งที่จำเป็นเท่านั้น เพื่อให้ R8 เพิ่มประสิทธิภาพส่วนอื่นๆ ได้
 

กฎคุณภาพ

 

-keep class com.example.** { ; }

 

ต่ำ: เก็บแพ็กเกจทั้งหมดและแพ็กเกจย่อย

 

-keep class com.example.MyClass { ; }

 

ต่ำ: เก็บทั้งชั้นเรียนซึ่งอาจยังกว้างเกินไป
-keepclassmembers class com.example.MyClass {

    private java.lang.String secretMessage;

    public void onNativeEvent(java.lang.String);

}
สูง: เก็บเฉพาะเมธอดและพร็อพเพอร์ตี้ที่เกี่ยวข้องจากคลาสที่เฉพาะเจาะจง

ใช้บรรพบุรุษร่วม

แทนที่จะเขียนกฎ Keep แยกกันสำหรับโมเดลข้อมูลที่แตกต่างกันหลายรายการ ให้เขียนกฎเดียวที่กำหนดเป้าหมายไปยังคลาสพื้นฐานหรืออินเทอร์เฟซทั่วไป กฎด้านล่างจะบอกให้ R8 เก็บสมาชิกของคลาสที่ใช้การติดตั้งใช้งานอินเทอร์เฟซนี้ไว้และปรับขนาดได้สูง

# Keep all fields of any class that implements SerializableModel

-keepclassmembers class * implements com.example.models.SerializableModel {

    <fields>;

}

ใช้คำอธิบายประกอบเพื่อกำหนดเป้าหมายหลายชั้นเรียน

สร้างคำอธิบายประกอบที่กำหนดเอง (เช่น @Serialize) แล้วใช้เพื่อ "ติดแท็ก" คลาสที่ต้องเก็บฟิลด์ไว้ นี่เป็นอีกรูปแบบหนึ่งที่สะอาด ประกาศ และปรับขนาดได้สูง คุณยังสร้างกฎ Keep สำหรับคำอธิบายประกอบที่มีอยู่แล้วจากเฟรมเวิร์กที่คุณใช้ได้ด้วย

# Keep all fields of any class annotated with @Serialize

-keepclassmembers class * {

    @com.example.annotations.Serialize <fields>;

}

เลือกตัวเลือก Keep ที่เหมาะสม

ตัวเลือก "เก็บ" เป็นส่วนที่สำคัญที่สุดของกฎ การเลือกที่ไม่ถูกต้องอาจปิดใช้การเพิ่มประสิทธิภาพโดยไม่จำเป็น

ตัวเลือก Keepสิ่งที่ทำ
-keepป้องกันไม่ให้ระบบนำคลาสและสมาชิกที่กล่าวถึงในการประกาศ ออกหรือเปลี่ยนชื่อ
-keepclassmembersป้องกันไม่ให้สมาชิกที่ระบุถูกนำออกหรือเปลี่ยนชื่อ แต่จะอนุญาตให้นำชั้นเรียนออกได้ แต่เฉพาะในชั้นเรียนที่ไม่ได้ถูกนำออกด้วยวิธีอื่น
-keepclasseswithmembersรวมกัน: เก็บทั้งคลาสและสมาชิกของคลาสก็ต่อเมื่อมีสมาชิกทั้งหมดที่ระบุ

ดูข้อมูลเพิ่มเติมเกี่ยวกับตัวเลือก Keep ได้ในเอกสารประกอบสำหรับตัวเลือก Keep

อนุญาตการเพิ่มประสิทธิภาพด้วยตัวแก้ไข

ตัวแก้ไขอย่าง allowshrinking และ allowobfuscation จะผ่อนปรนกฎ -keep ที่กว้างขวาง ทำให้ R8 มีอำนาจในการเพิ่มประสิทธิภาพอีกครั้ง ตัวอย่างเช่น หากไลบรารีเดิมบังคับให้คุณใช้ -keep ในทั้งคลาส คุณอาจเรียกคืนการเพิ่มประสิทธิภาพบางอย่างได้โดยอนุญาตการลดขนาดและการปกปิดโค้ด

# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it.

-keep,allowshrinking,allowobfuscation class com.example.LegacyClass

เพิ่มตัวเลือกส่วนกลางเพื่อการเพิ่มประสิทธิภาพเพิ่มเติม

นอกเหนือจากกฎการเก็บแล้ว คุณยังเพิ่มแฟล็กส่วนกลางลงในไฟล์กำหนดค่า R8 เพื่อกระตุ้นการเพิ่มประสิทธิภาพได้อีกด้วย

-repackageclasses เป็นตัวเลือกที่มีประสิทธิภาพซึ่งสั่งให้ R8 ย้ายคลาสที่คลุมเครือทั้งหมดไปยังแพ็กเกจเดียว ซึ่งจะช่วยประหยัดพื้นที่ในไฟล์ DEX ได้อย่างมากด้วยการนำสตริงชื่อแพ็กเกจที่ซ้ำกันออก

-allowaccessmodification ช่วยให้ R8 ขยายการเข้าถึง (เช่น private ไปยัง public) เพื่อเปิดใช้การแทรกอินไลน์ที่ดุดันมากขึ้น ตอนนี้ระบบจะเปิดใช้โดยค่าเริ่มต้นเมื่อใช้ proguard-android-optimize.txt

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

และเพื่อความชัดเจนยิ่งขึ้น ในปลั๊กอิน Android Gradle เวอร์ชัน 9.0 เราจะเริ่มไม่สนใจแฟล็กการเพิ่มประสิทธิภาพส่วนกลางจากไลบรารีทั้งหมด 

แนวทางปฏิบัติแนะนำสำหรับห้องสมุด

แอป Android ทุกแอปต้องใช้ไลบรารีไม่ทางใดก็ทางหนึ่ง มาดูแนวทางปฏิบัติแนะนำสำหรับไลบรารีกัน

สำหรับนักพัฒนาไลบรารี

หากไลบรารีใช้การสะท้อนหรือ JNI คุณมีหน้าที่ต้องระบุกฎการเก็บรักษาที่จำเป็นแก่ผู้ใช้ กฎเหล่านี้จะอยู่ในไฟล์ consumer-rules.pro ซึ่งจะรวมอยู่ในไฟล์ AAR ของไลบรารีโดยอัตโนมัติ

android {

    defaultConfig {

        consumerProguardFiles("consumer-rules.pro")

    }

    ...

}

สำหรับผู้ใช้ไลบรารี

กรองกฎ Keep ที่มีปัญหาออก

หากคุณต้องใช้ไลบรารีที่มีกฎ Keep ที่มีปัญหา คุณสามารถกรองกฎเหล่านั้นในไฟล์ build.gradle.kts ได้โดยเริ่มจาก AGP 9.0 ซึ่งจะบอกให้ R8 ละเว้นกฎที่มาจากทรัพยากร Dependency ที่เฉพาะเจาะจง

release {

    optimization.keepRules {

        // Ignore all consumer rules from this specific library

        it.ignoreFrom("com.somelibrary:somelibrary")

    }

}

กฎการเก็บรักษาที่ดีที่สุดคือกฎการเก็บรักษาที่ไม่มี

กลยุทธ์การกำหนดค่า R8 ขั้นสูงสุดคือการไม่จำเป็นต้องเขียนกฎ Keep เลย สำหรับแอปจำนวนมาก คุณสามารถทำได้โดยเลือกใช้ไลบรารีที่ทันสมัยซึ่งเน้นการสร้างโค้ดมากกว่าการสะท้อน การสร้างโค้ดช่วยให้เครื่องมือเพิ่มประสิทธิภาพระบุได้ง่ายขึ้นว่าโค้ดใดที่ใช้จริงในขณะรันไทม์และโค้ดใดที่นำออกได้ นอกจากนี้ การไม่ใช้การสะท้อนแบบไดนามิกยังหมายความว่าจะไม่มีจุดแรกเข้าที่ "ซ่อน" ไว้ ดังนั้นจึงไม่จำเป็นต้องมีกฎการเก็บรักษา เมื่อเลือกไลบรารีใหม่ ให้เลือกโซลูชันที่ใช้การสร้างโค้ดมากกว่าการสะท้อนเสมอ

ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีเลือกไลบรารีได้ที่เลือกไลบรารีอย่างชาญฉลาด

การแก้ไขข้อบกพร่องและการแก้ปัญหาการกำหนดค่า R8

เมื่อ R8 นำโค้ดที่ควรเก็บไว้ หรือ APK มีขนาดใหญ่กว่าที่คาดไว้ ให้ใช้เครื่องมือเหล่านี้เพื่อวินิจฉัยปัญหา

ค้นหากฎ Keep ที่ซ้ำกันและกฎ Keep ทั่วโลก

เนื่องจาก R8 ผสานกฎจากแหล่งที่มาหลายสิบแหล่ง จึงอาจยากที่จะทราบว่าชุดกฎ "สุดท้าย" คืออะไร การเพิ่มแฟล็กนี้ลงในไฟล์ proguard-rules.pro จะสร้างรายงานที่สมบูรณ์ดังนี้

# Outputs the final, merged set of rules to the specified file

-printconfiguration build/outputs/logs/configuration.txt

คุณสามารถค้นหาไฟล์นี้เพื่อหากฎที่ซ้ำกันหรือติดตามกฎที่มีปัญหา (เช่น -dontoptimize) กลับไปยังไลบรารีที่เฉพาะเจาะจงซึ่งมีกฎนั้น

Ask R8: Why are you keeping this?

หากชั้นเรียนที่คุณคาดว่าจะถูกนำออกยังคงอยู่ในแอป R8 จะบอกเหตุผลให้คุณทราบ เพียงเพิ่มกฎนี้

# Asks R8 to explain why it's keeping a specific class

class com.example.MyUnusedClass

-whyareyoukeeping 

ในระหว่างการสร้าง R8 จะพิมพ์ห่วงโซ่การอ้างอิงที่แน่นอนซึ่งทำให้ต้องเก็บคลาสนั้นไว้ เพื่อให้คุณติดตามการอ้างอิงและปรับกฎได้

ดูคำแนะนำแบบเต็มได้ที่ส่วนการแก้ปัญหา R8

ขั้นตอนถัดไป

R8 เป็นเครื่องมือที่มีประสิทธิภาพในการปรับปรุงประสิทธิภาพของแอป Android ประสิทธิภาพของเครื่องมือนี้ขึ้นอยู่กับความเข้าใจที่ถูกต้องเกี่ยวกับการทำงานของเครื่องมือในฐานะเครื่องมือวิเคราะห์แบบคงที่

การเขียนกฎระดับสมาชิกที่เฉพาะเจาะจง การใช้ประโยชน์จากบรรพบุรุษและคำอธิบายประกอบ รวมถึงการเลือกตัวเลือกการเก็บรักษาที่เหมาะสมอย่างรอบคอบจะช่วยให้คุณเก็บรักษาเฉพาะสิ่งที่จำเป็นได้ แนวทางปฏิบัติที่ซับซ้อนที่สุดคือการไม่จำเป็นต้องใช้กฎเลยโดยเลือกใช้ไลบรารีที่ทันสมัยซึ่งอิงตาม Codegen แทนไลบรารีรุ่นก่อนหน้าที่อิงตามการสะท้อน

ในสัปดาห์ที่เน้นประสิทธิภาพนี้ อย่าลืมดูวิดีโอ Spotlight Week ของวันนี้บน YouTube และร่วมสนุกกับความท้าทาย R8 ของเราต่อไป ใช้ #optimizationEnabled หากมีคำถามเกี่ยวกับการเปิดใช้หรือการแก้ปัญหา R8 เราพร้อมช่วยเหลือคุณ

ได้เวลาดูสิทธิประโยชน์ด้วยตัวคุณเองแล้ว

เราขอท้าให้คุณเปิดใช้โหมดเต็มรูปแบบของ R8 สำหรับแอปตั้งแต่วันนี้

  1. ทำตามคู่มือนักพัฒนาแอปเพื่อเริ่มต้นใช้งาน: เปิดใช้การเพิ่มประสิทธิภาพแอป
  2. ตรวจสอบว่าคุณยังใช้ proguard-android.txt อยู่หรือไม่ แล้วแทนที่ด้วย proguard-android-optimize.txt
  3. จากนั้นวัดผลลัพธ์ อย่าแค่รู้สึกถึงความแตกต่าง แต่ให้ยืนยันความแตกต่างนั้น วัดผลลัพธ์ด้านประสิทธิภาพโดยการปรับโค้ดจาก แอปตัวอย่าง Macrobenchmark ใน GitHub เพื่อวัดเวลาเริ่มต้นก่อนและหลัง

เรามั่นใจว่าคุณจะเห็นประสิทธิภาพของแอปดีขึ้นอย่างมีนัยสำคัญ

และอย่าลืมใช้แฮชแท็ก #AskAndroid เพื่อถามคำถามด้วย ผู้เชี่ยวชาญของเราจะตรวจสอบและตอบคำถามของคุณตลอดทั้งสัปดาห์

โปรดติดตามข้อมูลเพิ่มเติมในวันพรุ่งนี้ ซึ่งเราจะพูดถึงการเพิ่มประสิทธิภาพที่แนะนำโดยโปรไฟล์ด้วยโปรไฟล์พื้นฐานและโปรไฟล์เริ่มต้น รวมถึงแชร์วิธีที่ประสิทธิภาพการแสดงผลของ Compose ดีขึ้นในช่วงที่ผ่านมา และแชร์ข้อควรพิจารณาด้านประสิทธิภาพสำหรับงานที่ทำงานในเบื้องหลัง

เขียนโดย

อ่านต่อ