การเพิ่มประสิทธิภาพสำหรับผู้เขียนคลัง

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

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

หากคุณเป็นนักพัฒนาแอปและต้องการเรียนรู้เกี่ยวกับการเพิ่มประสิทธิภาพแอป Android โปรดดูหัวข้อเปิดใช้การเพิ่มประสิทธิภาพแอป หากต้องการดูข้อมูลเกี่ยวกับไลบรารีที่เหมาะกับการใช้งาน โปรดดูหัวข้อเลือกไลบรารีอย่างชาญฉลาด

ทำความเข้าใจประเภทกฎการเก็บรักษา

กฎการเก็บรักษาในไลบรารีมี 2 ประเภทที่แตกต่างกัน ได้แก่

  • กฎการเก็บรักษาสำหรับผู้ใช้ ต้องระบุกฎที่เก็บรักษาทุกสิ่งที่ไลบรารีสะท้อนออกมา หากไลบรารีใช้การสะท้อนหรือ JNI เพื่อเรียกใช้โค้ดของตนเองหรือโค้ดที่กำหนดโดยแอปของไคลเอ็นต์ กฎเหล่านี้ต้องอธิบายโค้ดที่ต้องเก็บรักษาไว้ ไลบรารีควรแพ็กเกจกฎการเก็บรักษาสำหรับผู้ใช้ซึ่งใช้รูปแบบเดียวกับกฎการเก็บรักษาของแอป กฎเหล่านี้จะรวมอยู่ในอาร์ติแฟกต์ของไลบรารี (AAR หรือ JAR) และระบบจะใช้กฎเหล่านี้โดยอัตโนมัติระหว่างการเพิ่มประสิทธิภาพแอป Android เมื่อมีการใช้ไลบรารี กฎเหล่านี้จะได้รับการเก็บรักษาไว้ในไฟล์ที่ระบุด้วยพร็อพเพอร์ตี้ consumerProguardFiles ในไฟล์ build.gradle.kts (หรือ build.gradle) ดูข้อมูลเพิ่มเติมได้ที่เขียนกฎการเก็บรักษาสำหรับผู้ใช้
  • กฎการเก็บรักษาสำหรับการสร้างไลบรารี จะมีผลเมื่อมีการสร้างไลบรารี กฎเหล่านี้จำเป็นก็ต่อเมื่อคุณตัดสินใจเพิ่มประสิทธิภาพไลบรารีบางส่วนในระหว่างการสร้าง กฎเหล่านี้ต้องเก็บรักษา API สาธารณะของไลบรารีไว้ไม่ให้ถูกนำออก ไม่เช่นนั้น API สาธารณะจะไม่อยู่ในการเผยแพร่ไลบรารี ซึ่งหมายความว่านักพัฒนาแอปจะไม่สามารถใช้ไลบรารีได้ กฎเหล่านี้จะได้รับการเก็บรักษาไว้ในไฟล์ ที่ระบุด้วยพร็อพเพอร์ตี้ proguardFiles ในไฟล์ build.gradle.kts (หรือ build.gradle) ดูข้อมูลเพิ่มเติมได้ที่ เพิ่มประสิทธิภาพการสร้างไลบรารี AAR

ข้อกำหนดและหลักเกณฑ์ของการเพิ่มประสิทธิภาพ

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

ปฏิบัติตามข้อกำหนดของการเพิ่มประสิทธิภาพ

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

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

    อย่าใส่กฎการเก็บรักษาระดับแพ็กเกจ (เช่น -keep class com.mylibrary.** {*; }) สำหรับแพ็กเกจในไลบรารีหรือไลบรารีอื่นๆ ที่อ้างอิง กฎดังกล่าวจะจำกัดการเพิ่มประสิทธิภาพสำหรับแพ็กเกจเหล่านี้ในแอปทั้งหมดที่ใช้ไลบรารีของคุณ

  • ไม่มีกฎส่วนกลางที่ไม่เหมาะสม: อย่าใช้ ตัวเลือกส่วนกลาง เช่น -dontobfuscate หรือ -allowaccessmodification

  • ใช้ codegen แทนการสะท้อนทุกครั้งที่ทำได้: เมื่อทำได้ ให้ใช้ การสร้างโค้ด (codegen) แทนการสะท้อน ทั้ง codegen และการสะท้อนเป็นแนวทางที่ใช้กันทั่วไปเพื่อหลีกเลี่ยงโค้ด Boilerplate เมื่อเขียนโปรแกรม แต่ codegen เข้ากันได้กับเครื่องมือเพิ่มประสิทธิภาพแอป เช่น R8 มากกว่า

    codegen จะวิเคราะห์และแก้ไขโค้ดในระหว่างกระบวนการบิลด์ เนื่องจากไม่มีการแก้ไขที่สำคัญหลังจากการคอมไพล์ เครื่องมือเพิ่มประสิทธิภาพจึงทราบว่าโค้ดใดจำเป็นในท้ายที่สุดและโค้ดใดที่นำออกได้อย่างปลอดภัย

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

    ไลบรารีสมัยใหม่จำนวนมากใช้ codegen แทนการสะท้อน ดู KSP สำหรับจุดเริ่มต้นทั่วไปที่ Room, Dagger2 และ อื่นๆ อีกมากมายใช้

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

คำแนะนำเพิ่มเติม

นอกเหนือจากข้อกำหนดของการเพิ่มประสิทธิภาพแล้ว คำแนะนำเพิ่มเติมมีดังนี้

  • อย่าใช้ -repackageclasses ในไฟล์กฎการเก็บรักษาสำหรับผู้ใช้ของไลบรารี อย่างไรก็ตาม หากต้องการเพิ่มประสิทธิภาพการสร้างไลบรารี คุณสามารถใช้ -repackageclasses กับชื่อแพ็กเกจภายใน เช่น <your.library.package>.internal ใน ไฟล์กฎการเก็บรักษาสำหรับการสร้างไลบรารี ซึ่งจะช่วยเพิ่มประสิทธิภาพของไลบรารีในแอปที่ไม่ได้เพิ่มประสิทธิภาพ อย่างไรก็ตาม โดยทั่วไปแล้วไม่จำเป็นต้องดำเนินการนี้ เนื่องจากแอปควรได้รับการเพิ่มประสิทธิภาพด้วย
  • ประกาศแอตทริบิวต์ที่จำเป็นเพื่อให้ไลบรารีทำงานได้ในไฟล์กฎการเก็บรักษาของไลบรารี แม้ว่าอาจมีการทับซ้อนกับแอตทริบิวต์ที่กำหนดไว้ใน proguard-android-optimize.txt
  • หากคุณต้องการแอตทริบิวต์ต่อไปนี้ในการเผยแพร่ไลบรารี ให้เก็บรักษาแอตทริบิวต์เหล่านี้ไว้ในไฟล์กฎการเก็บรักษาสำหรับการสร้างไลบรารี ไม่ใช่ ในไฟล์กฎการเก็บรักษาสำหรับผู้ใช้ของไลบรารี
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • ผู้เขียนไลบรารีควรเก็บรักษาแอตทริบิวต์ RuntimeVisibleAnnotations ไว้ในกฎการเก็บรักษาสำหรับผู้ใช้ หากมีการใช้คำอธิบายประกอบในรันไทม์
  • ผู้เขียนไลบรารีไม่ควรใช้ตัวเลือกส่วนกลางต่อไปนี้ในกฎการเก็บรักษาสำหรับผู้ใช้
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

กรณีที่ใช้การสะท้อนได้

หากต้องใช้การสะท้อน คุณควรสะท้อนเฉพาะสิ่งใดสิ่งหนึ่งต่อไปนี้

  • ประเภทที่กำหนดเป้าหมายที่เฉพาะเจาะจง (ผู้ใช้ Interface ที่เฉพาะเจาะจงหรือคลาสย่อย)
  • โค้ดที่ใช้คำอธิบายประกอบรันไทม์ที่เฉพาะเจาะจง

การใช้การสะท้อนในลักษณะนี้จะจำกัดต้นทุนรันไทม์และช่วยให้เขียน กฎการเก็บรักษาสำหรับผู้ใช้ที่กำหนดเป้าหมายได้

การสะท้อนรูปแบบที่เฉพาะเจาะจงและกำหนดเป้าหมายนี้เป็นรูปแบบที่คุณเห็นได้ทั้งในเฟรมเวิร์ก Android (เช่น เมื่อขยายกิจกรรม มุมมอง และ Drawable) และไลบรารี AndroidX (เช่น เมื่อสร้าง WorkManager ListenableWorkers หรือ RoomDatabases) ในทางตรงกันข้าม การสะท้อนแบบเปิดของ Gson ไม่เหมาะกับการใช้งานในแอป Android

ความเข้าใจผิดที่พบบ่อย

ความเข้าใจผิดที่พบบ่อยบางอย่างอาจทำให้คุณกำหนดค่า R8 ไม่ถูกต้อง ซึ่งรวมถึงความเข้าใจผิดต่อไปนี้

  • ความเข้าใจผิดเกี่ยวกับการเพิ่มประสิทธิภาพของ R8: ตรงกันข้ามกับความเข้าใจทั่วไป การเพิ่มประสิทธิภาพของ R8 ไม่ได้จำกัดอยู่แค่การปรับโค้ดให้ยากต่อการอ่านเท่านั้น แต่ยังรวมถึงการลดขนาดโค้ดและการเพิ่มประสิทธิภาพเชิงตรรกะด้วยเทคนิคการแทรกเมธอดและการผสานคลาส ดูข้อมูลเพิ่มเติมได้ที่ ภาพรวม การเพิ่มประสิทธิภาพ R8

  • การข้ามการเพิ่มประสิทธิภาพไลบรารีที่ปรับโค้ดให้ยากต่อการอ่าน: ข้อผิดพลาดที่พบบ่อยคือการ ละเว้นไลบรารีจากการเพิ่มประสิทธิภาพ เนื่องจากไลบรารีได้รับการเพิ่มประสิทธิภาพหรือ ปรับโค้ดให้ยากต่อการอ่านเมื่อคอมไพล์เป็น AAR (Android Archive) หรือ JAR (Java Archive) การเพิ่มประสิทธิภาพในระหว่างเวลาบิลด์ไลบรารีมีข้อจำกัด และแอปไม่ควรปิดใช้การเพิ่มประสิทธิภาพของไลบรารีโดยการรวมไลบรารีไว้ในกฎการเก็บรักษา ดูข้อมูลเพิ่มเติมได้ที่เพิ่มประสิทธิภาพการสร้างไลบรารี AAR

  • ความเข้าใจผิดเกี่ยวกับตัวเลือก -keep กฎ -keep จะป้องกันไม่ให้ R8 เรียกใช้ การเพิ่มประสิทธิภาพ ใดๆ ดูข้อมูลเพิ่มเติมได้ที่ ดู เลือกตัวเลือกการเก็บรักษาที่เหมาะสม

กำหนดค่าการแพ็กเกจกฎ

หากต้องการให้กฎการเก็บรักษาสำหรับผู้ใช้มีผลอย่างถูกต้อง คุณต้องแพ็กเกจกฎเหล่านี้อย่างเหมาะสมตามรูปแบบไลบรารี

ไลบรารี AAR

หากต้องการเพิ่มกฎสำหรับผู้ใช้สำหรับไลบรารี AAR ให้ใช้ตัวเลือก consumerProguardFiles ในสคริปต์การสร้างของโมดูลไลบรารี Android ดูข้อมูลเพิ่มเติมได้ใน คำแนะนำเกี่ยวกับการสร้างโมดูลไลบรารี

Kotlin

android {
    defaultConfig {
        consumerProguardFiles("consumer-proguard-rules.pro")
    }
    ...
}

ดึงดูด

android {
    defaultConfig {
        consumerProguardFiles 'consumer-proguard-rules.pro'
    }
    ...
}

ไลบรารี JAR

หากต้องการรวมกฎกับไลบรารี Kotlin หรือ Java ที่เผยแพร่เป็น JAR ให้วางไฟล์กฎไว้ในไดเรกทอรี META-INF/proguard/ ของ JAR สุดท้าย โดยใช้ชื่อไฟล์ใดก็ได้ ตัวอย่างเช่น หากโค้ดอยู่ใน <libraryroot>/src/main/kotlin ให้วางไฟล์กฎสำหรับผู้ใช้ไว้ที่ <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro แล้วระบบจะรวมกฎไว้ในตำแหน่งที่ถูกต้องใน JAR เอาต์พุต

ตรวจสอบว่า JAR สุดท้ายรวมกฎอย่างถูกต้องโดยดูว่ากฎอยู่ในไดเรกทอรี META-INF/proguard หรือไม่

เพิ่มประสิทธิภาพการสร้างไลบรารี AAR (ขั้นสูง)

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

หากยังต้องการเพิ่มประสิทธิภาพไลบรารีในระหว่างเวลาบิลด์ ปลั๊กอิน Android Gradle จะรองรับการดำเนินการนี้

Kotlin

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        configureEach {
            consumerProguardFiles("consumer-rules.pro")
        }
    }
}

ดึงดูด

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                'proguard-rules.pro'
        }
        configureEach {
            consumerProguardFiles "consumer-rules.pro"
        }
    }
}

โปรดทราบว่าลักษณะการทำงานของ proguardFiles แตกต่างจาก consumerProguardFiles มาก

  • proguardFiles ใช้ในระหว่างเวลาบิลด์ โดยมักจะใช้ร่วมกับ getDefaultProguardFile("proguard-android-optimize.txt") เพื่อกำหนดส่วนของไลบรารีที่ควรเก็บรักษาไว้ในระหว่างการสร้างไลบรารี ซึ่งอย่างน้อยที่สุดก็คือ API สาธารณะ
  • ในทางตรงกันข้าม consumerProguardFiles จะรวมอยู่ในไลบรารีเพื่อส่งผลต่อการเพิ่มประสิทธิภาพที่จะเกิดขึ้นในภายหลัง ในระหว่างการสร้างแอปที่ใช้ไลบรารีของคุณ

ตัวอย่างเช่น หากไลบรารีใช้การสะท้อนเพื่อสร้างคลาสภายใน คุณอาจต้องกำหนดกฎการเก็บรักษาทั้งใน proguardFiles และ consumerProguardFiles

หากคุณใช้ -repackageclasses ในบิลด์ไลบรารี ให้จัดแพ็กเกจคลาสใหม่เป็นแพ็กเกจย่อย ภายใน แพ็กเกจของไลบรารี ตัวอย่างเช่น ให้ใช้ -repackageclasses 'com.example.mylibrary.internal' แทน -repackageclasses 'internal'

รองรับ R8 เวอร์ชันต่างๆ (ขั้นสูง)

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

หากต้องการระบุกฎ R8 ที่กำหนดเป้าหมาย คุณต้องรวมกฎเหล่านั้นไว้ในไดเรกทอรี META-INF/com.android.tools ภายใน classes.jar ของ AAR หรือในไดเรกทอรี META-INF/com.android.tools ของ JAR

In an AAR library:
    proguard.txt (legacy location, the file name must be "proguard.txt")
    classes.jar
    └── META-INF
        └── com.android.tools (location of targeted R8 rules)
            ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
            └── ... (more directories with the same name format)

In a JAR library:
    META-INF
    ├── proguard/<ProGuard-rule-files> (legacy location)
    └── com.android.tools (location of targeted R8 rules)
        ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
        └── ... (more directories with the same name format)

ในไดเรกทอรี META-INF/com.android.tools อาจมี ไดเรกทอรีย่อยหลายรายการที่มีชื่อในรูปแบบ r8-from-<X>-upto-<Y> เพื่อระบุ R8 เวอร์ชันที่เขียนกฎไว้ แต่ละไดเรกทอรีย่อยอาจมีไฟล์อย่างน้อย 1 ไฟล์ที่มีกฎ R8 โดยใช้ชื่อไฟล์และนามสกุลใดก็ได้

โปรดทราบว่าส่วน -from-<X> และ -upto-<Y> เป็นส่วนที่ไม่บังคับ เวอร์ชัน <Y> เป็นเวอร์ชัน เฉพาะ และช่วงเวอร์ชันมักจะเป็นช่วงต่อเนื่อง แต่ก็อาจทับซ้อนกันได้

ตัวอย่างเช่น r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 และ r8-from-8.2.0 เป็นชื่อไดเรกทอรีที่แสดงชุดกฎ R8 ที่กำหนดเป้าหมาย กฎในไดเรกทอรี r8 สามารถใช้ได้กับ R8 ทุกเวอร์ชัน กฎในไดเรกทอรี r8-from-8.0.0-upto-8.2.0 สามารถใช้ได้กับ R8 เวอร์ชัน 8.0.0 ขึ้นไป แต่ไม่รวม เวอร์ชัน 8.2.0

ปลั๊กอิน Android Gradle ใช้ข้อมูลดังกล่าวเพื่อเลือกกฎทั้งหมดที่ R8 เวอร์ชันปัจจุบันใช้ได้ หากไลบรารีไม่ได้ระบุกฎ R8 ที่กำหนดเป้าหมาย ปลั๊กอิน Android Gradle จะเลือกกฎจากตำแหน่งเดิม (proguard.txt สำหรับ AAR หรือ META-INF/proguard/<ProGuard-rule-files> สำหรับ JAR)