ในฐานะผู้เขียนไลบรารี คุณต้องดูแลให้นักพัฒนาแอปสามารถรวมไลบรารีของคุณเข้ากับแอปได้อย่างง่ายดาย พร้อมทั้งรักษาประสบการณ์การใช้งานของผู้ใช้ปลายทางให้มีคุณภาพสูง ซึ่งหมายความว่าไลบรารีของคุณต้องเข้ากันได้กับการเพิ่มประสิทธิภาพ 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 - หากคุณต้องการแอตทริบิวต์ต่อไปนี้ในการเผยแพร่ไลบรารี ให้เก็บรักษาแอตทริบิวต์เหล่านี้ไว้ในไฟล์กฎการเก็บรักษาสำหรับการสร้างไลบรารี ไม่ใช่ ในไฟล์กฎการเก็บรักษาสำหรับผู้ใช้ของไลบรารี
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- ผู้เขียนไลบรารีควรเก็บรักษาแอตทริบิวต์
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)