Là tác giả thư viện, bạn phải đảm bảo rằng nhà phát triển ứng dụng có thể dễ dàng kết hợp thư viện của bạn vào ứng dụng của họ trong khi vẫn duy trì trải nghiệm chất lượng cao cho người dùng cuối. Điều này có nghĩa là thư viện của bạn phải tương thích với tính năng tối ưu hoá Android (R8) mà không yêu cầu nhà phát triển thiết lập thêm – hoặc ghi lại rằng thư viện có thể không phù hợp để sử dụng trên Android. Điều quan trọng là các thư viện dự định sử dụng trên Android không được ngăn chặn các hoạt động tối ưu hoá quan trọng của ứng dụng và phải tuân thủ các yêu cầu tối ưu hoá bổ sung.
Tài liệu này nhắm đến nhà phát triển của các thư viện đã phát hành, nhưng cũng có thể hữu ích cho nhà phát triển của các mô-đun thư viện nội bộ trong một ứng dụng lớn, được mô-đun hoá.
Nếu bạn là nhà phát triển ứng dụng và muốn tìm hiểu về cách tối ưu hoá ứng dụng Android, hãy xem Bật tính năng tối ưu hoá ứng dụng. Để tìm hiểu về những thư viện phù hợp để sử dụng, hãy xem bài viết Chọn thư viện một cách khôn ngoan.
Tìm hiểu về các loại quy tắc giữ lại
Có 2 loại quy tắc giữ lại riêng biệt mà bạn có thể có trong thư viện:
- Quy tắc giữ lại của bên sử dụng thư viện phải chỉ định các quy tắc giữ lại mọi thứ mà thư viện phản ánh. Nếu một thư viện sử dụng tính năng phản chiếu hoặc JNI để gọi vào mã của thư viện hoặc mã do ứng dụng khách xác định, thì các quy tắc này cần mô tả mã nào cần được giữ lại. Thư viện nên đóng gói các quy tắc giữ lại của bên sử dụng thư viện, sử dụng cùng một định dạng như các quy tắc giữ lại của ứng dụng. Các quy tắc này được gói vào các cấu phần phần mềm thư viện (AAR hoặc JAR) và được sử dụng tự động trong quá trình tối ưu hoá ứng dụng Android khi thư viện được sử dụng. Các quy tắc này được duy trì trong tệp được chỉ định bằng thuộc tính
consumerProguardFilestrong tệpbuild.gradle.kts(hoặcbuild.gradle). Để tìm hiểu thêm, hãy xem bài viết Viết quy tắc giữ lại của bên sử dụng thư viện. - Quy tắc giữ lại của bản dựng thư viện được áp dụng khi thư viện của bạn được xây dựng. Bạn chỉ cần các quy tắc này nếu quyết định tối ưu hoá một phần thư viện của mình tại thời điểm xây dựng. Các quy tắc này phải giữ lại API công khai của thư viện để không bị xoá, nếu không, API công khai sẽ không có trong bản phân phối thư viện, nghĩa là nhà phát triển ứng dụng không thể sử dụng thư viện. Các quy tắc này được duy trì trong tệp
được chỉ định bằng thuộc tính
proguardFilestrong tệpbuild.gradle.kts(hoặcbuild.gradle). Để tìm hiểu thêm, hãy xem bài viết Tối ưu hoá bản dựng thư viện AAR.
Yêu cầu và nguyên tắc tối ưu hoá
Cấu hình R8 trong thư viện có tác động chung đến kích thước nhị phân cuối cùng và hiệu suất của ứng dụng sử dụng. Ngoài các phương pháp hay nhất chung về quy tắc giữ lại keep rule best practices, tác giả thư viện phải tuân thủ các yêu cầu cụ thể và cân nhắc các nguyên tắc bổ sung.
Tuân thủ các yêu cầu tối ưu hoá
Hiệu suất kém trong thư viện là nguyên nhân chính khiến ứng dụng bị phình to, lãng phí bộ nhớ, khởi động chậm và lỗi ANR (Ứng dụng không phản hồi). Thư viện phải tránh vi phạm các yêu cầu sau để không làm giảm đáng kể chất lượng ứng dụng và trải nghiệm người dùng.
Không có quy tắc giữ lại rộng hoặc trên toàn gói: Thư viện của bạn không được bao gồm các quy tắc giữ lại rộng giữ lại hầu hết mã trong thư viện của bạn hoặc trong một thư viện khác. Các quy tắc giữ lại rộng có thể giải quyết các sự cố trong ngắn hạn, nhưng chúng làm phình to kích thước ứng dụng của tất cả các ứng dụng sử dụng thư viện của bạn.
Không đưa các quy tắc giữ lại trên toàn gói (chẳng hạn như
-keep class com.mylibrary.** {*; }) vào các gói trong thư viện của bạn hoặc các thư viện được tham chiếu khác. Các quy tắc như vậy giới hạn việc tối ưu hoá cho các gói này trên tất cả các ứng dụng sử dụng thư viện của bạn.Không có quy tắc chung không phù hợp: Không bao giờ sử dụng các lựa chọn chung như
-dontobfuscatehoặc-allowaccessmodification.Sử dụng codegen thay vì phản chiếu bất cứ khi nào có thể: Khi có thể, hãy sử dụng tính năng tạo mã (codegen) thay vì phản chiếu. Cả codegen và phản chiếu đều là các phương pháp phổ biến để tránh mã nguyên mẫu khi lập trình, nhưng codegen tương thích hơn với trình tối ưu hoá ứng dụng như R8.
Với codegen, mã được phân tích và sửa đổi trong quá trình xây dựng. Vì không có sửa đổi lớn nào sau thời gian biên dịch, nên trình tối ưu hoá biết mã nào cần thiết và mã nào có thể được xoá một cách an toàn.
Với tính năng phản chiếu, mã được phân tích và thao tác trong thời gian chạy. Vì mã không thực sự được hoàn thiện cho đến khi thực thi, nên trình tối ưu hoá không biết mã nào có thể được xoá một cách an toàn. Trình tối ưu hoá có thể sẽ xoá mã được sử dụng linh hoạt thông qua tính năng phản chiếu trong thời gian chạy, điều này gây ra sự cố ứng dụng cho người dùng.
Nhiều thư viện hiện đại sử dụng codegen thay vì phản chiếu. Hãy xem KSP để biết điểm truy cập chung, được Room, Dagger2 và nhiều thư viện khác sử dụng.
Hỗ trợ chế độ đầy đủ chức năng của R8: Thư viện của bạn không được gặp sự cố khi chế độ đầy đủ chức năng của R8 được bật. Chế độ đầy đủ chức năng của R8 là chế độ được đề xuất để sử dụng R8 và là chế độ mặc định kể từ AGP 8.0 (đã ổn định vào năm 2023). Nếu thư viện của bạn gặp sự cố trong R8, giải pháp là xác định điểm truy cập phản chiếu hoặc JNI cụ thể và thêm quy tắc nhắm mục tiêu, không giữ lại toàn bộ gói.
Đề xuất khác
Ngoài các yêu cầu tối ưu hoá, sau đây là các đề xuất khác.
- Không sử dụng
-repackageclassestrong tệp quy tắc giữ lại của bên sử dụng thư viện. Tuy nhiên, để tối ưu hoá bản dựng thư viện, bạn có thể sử dụng-repackageclassesvới tên gói nội bộ, chẳng hạn như<your.library.package>.internal, trong tệp quy tắc giữ lại của bản dựng thư viện. Điều này có thể cải thiện hiệu quả của thư viện trong các ứng dụng chưa được tối ưu hoá. Tuy nhiên, điều này thường không cần thiết vì các ứng dụng cũng cần được tối ưu hoá. - Khai báo mọi thuộc tính bạn cần để thư viện hoạt động trong các tệp quy tắc giữ lại của thư viện, ngay cả khi có thể có sự trùng lặp với các thuộc tính được xác định trong
proguard-android-optimize.txt. - Nếu bạn yêu cầu các thuộc tính sau trong bản phân phối thư viện, hãy duy trì các thuộc tính đó trong tệp quy tắc giữ lại của bản dựng thư viện và không trong tệp quy tắc giữ lại của bên sử dụng thư viện:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- Tác giả thư viện nên giữ lại thuộc tính
RuntimeVisibleAnnotationstrong các quy tắc giữ lại của bên sử dụng thư viện nếu chú thích được sử dụng trong thời gian chạy. - Tác giả thư viện không nên sử dụng các lựa chọn chung sau trong các quy tắc giữ lại của bên sử dụng thư viện:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
Khi nào thì có thể sử dụng tính năng phản chiếu
Nếu phải sử dụng tính năng phản chiếu, bạn chỉ nên phản chiếu vào một trong những nội dung sau:
- Các loại được nhắm mục tiêu cụ thể (các trình triển khai giao diện hoặc lớp con cụ thể)
- Mã sử dụng chú thích thời gian chạy cụ thể
Việc sử dụng tính năng phản chiếu theo cách này sẽ giới hạn chi phí thời gian chạy và cho phép viết các quy tắc giữ lại của bên sử dụng thư viện được nhắm mục tiêu.
Dạng phản chiếu cụ thể và được nhắm mục tiêu này là một mẫu mà bạn có thể thấy trên cả khung Android (ví dụ: khi mở rộng các hoạt động, khung hiển thị và thành phần có thể vẽ) và thư viện AndroidX (ví dụ: khi tạo WorkManager
ListenableWorkers hoặc RoomDatabases). Ngược lại, tính năng phản chiếu mở của Gson không phù hợp để sử dụng trong các ứng dụng Android.
Quan niệm sai lầm phổ biến
Một vài quan niệm sai lầm phổ biến có thể khiến bạn định cấu hình R8 không chính xác. Các quan niệm sai lầm này bao gồm:
Hiểu không chính xác về các hoạt động tối ưu hoá của R8: Trái với sự hiểu biết phổ biến, các hoạt động tối ưu hoá của R8 không chỉ giới hạn ở việc làm rối mã nguồn mà còn bao gồm cả việc rút gọn mã và tối ưu hoá logic bằng các kỹ thuật nội tuyến phương thức và hợp nhất lớp. Để biết thêm thông tin, hãy xem Tổng quan về hoạt động tối ưu hoá R8.
Bỏ qua việc tối ưu hoá các thư viện bị làm rối mã nguồn: Một lỗi thường gặp là bỏ qua một thư viện khỏi quá trình tối ưu hoá vì thư viện đó đã được tối ưu hoá hoặc làm rối mã nguồn khi được biên dịch thành AAR (Tệp lưu trữ Android) hoặc JAR (Tệp lưu trữ Java Archive). Các hoạt động tối ưu hoá trong thời gian xây dựng thư viện bị giới hạn và ứng dụng của bạn không được tắt tính năng tối ưu hoá thư viện bằng cách đưa thư viện đó vào quy tắc giữ lại. Để biết thêm thông tin, hãy xem bài viết Tối ưu hoá bản dựng thư viện AAR.
Hiểu không chính xác về lựa chọn
-keepQuy tắc-keepngăn R8 chạy bất kỳ lần tối ưu hoá nào. Để biết thêm thông tin, xem Chọn lựa chọn giữ lại phù hợp.
Định cấu hình việc đóng gói quy tắc
Để đảm bảo các quy tắc giữ lại của bên sử dụng thư viện được áp dụng chính xác, bạn phải đóng gói các quy tắc đó một cách thích hợp tuỳ thuộc vào định dạng thư viện của bạn.
Thư viện AAR
Để thêm các quy tắc của bên sử dụng thư viện AAR, hãy sử dụng lựa chọn consumerProguardFiles trong tập lệnh xây dựng của mô-đun thư viện Android. Để biết thêm thông tin, hãy xem
hướng dẫn của chúng tôi về cách tạo mô-đun thư viện.
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
Groovy
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
Thư viện JAR
Để gói các quy tắc với thư viện Kotlin hoặc Java được phân phối dưới dạng JAR, hãy đặt tệp quy tắc của bạn vào thư mục META-INF/proguard/ của JAR cuối cùng, với bất kỳ tên tệp nào.
Ví dụ: nếu mã của bạn nằm trong <libraryroot>/src/main/kotlin, hãy đặt tệp quy tắc của bên sử dụng thư viện tại
<libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro
và các quy tắc sẽ được gói ở đúng vị trí trong JAR đầu ra của bạn.
Xác minh rằng JAR cuối cùng gói các quy tắc chính xác bằng cách kiểm tra để đảm bảo rằng các quy tắc nằm trong thư mục META-INF/proguard.
Tối ưu hoá bản dựng thư viện AAR (nâng cao)
Nói chung, bạn không cần tối ưu hoá trực tiếp bản dựng thư viện vì các hoạt động tối ưu hoá có thể thực hiện tại thời gian xây dựng thư viện rất hạn chế. Là nhà phát triển thư viện, bạn cần cân nhắc về nhiều giai đoạn tối ưu hoá và giữ lại hành vi, cả ở thời gian xây dựng thư viện và ứng dụng, trước khi tối ưu hoá thư viện đó.
Nếu bạn vẫn muốn tối ưu hoá thư viện của mình tại thời gian xây dựng, thì trình bổ trợ Android cho Gradle sẽ hỗ trợ việc này.
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
Groovy
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
Xin lưu ý rằng hành vi của proguardFiles rất khác với consumerProguardFiles:
proguardFilesđược sử dụng tại thời điểm xây dựng, thường cùng vớigetDefaultProguardFile("proguard-android-optimize.txt"), để xác định phần nào của thư viện cần được giữ lại trong quá trình xây dựng thư viện. Tối thiểu, đây là API công khai của bạn.- Ngược lại,
consumerProguardFilesđược đóng gói vào thư viện để ảnh hưởng đến những hoạt động tối ưu hoá diễn ra sau đó, trong quá trình xây dựng ứng dụng sử dụng thư viện của bạn.
Ví dụ: nếu thư viện của bạn sử dụng tính năng phản chiếu để tạo các lớp nội bộ, bạn có thể cần xác định các quy tắc giữ lại trong cả proguardFiles và consumerProguardFiles.
Nếu bạn sử dụng -repackageclasses trong bản dựng thư viện, hãy đóng gói lại các lớp vào một gói con bên trong gói thư viện của bạn. Ví dụ: hãy sử dụng -repackageclasses
'com.example.mylibrary.internal' thay vì -repackageclasses 'internal'.
Hỗ trợ nhiều phiên bản R8 (nâng cao)
Bạn có thể điều chỉnh các quy tắc để nhắm đến các phiên bản cụ thể của R8. Điều này cho phép thư viện của bạn hoạt động tối ưu trong các dự án sử dụng phiên bản R8 mới hơn, đồng thời cho phép tiếp tục sử dụng các quy tắc hiện có trong các dự án có phiên bản R8 cũ hơn.
Để chỉ định các quy tắc R8 được nhắm mục tiêu, bạn cần đưa các quy tắc đó vào thư mục META-INF/com.android.tools bên trong classes.jar của AAR hoặc trong thư mục META-INF/com.android.tools của 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)
Trong thư mục META-INF/com.android.tools, có thể có nhiều
thư mục con có tên ở dạng r8-from-<X>-upto-<Y> để cho biết
các quy tắc được viết cho phiên bản R8 nào. Mỗi thư mục con có thể có một hoặc nhiều tệp chứa các quy tắc R8, với bất kỳ tên tệp và đuôi tệp nào.
Xin lưu ý rằng các phần -from-<X> và -upto-<Y> là không bắt buộc, phiên bản <Y>
là loại trừ và các dải phiên bản thường liên tục nhưng cũng có thể
trùng lặp.
Ví dụ: r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 và
r8-from-8.2.0 là tên thư mục đại diện cho một tập hợp các quy tắc R8 được nhắm mục tiêu. Mọi phiên bản R8 đều có thể sử dụng các quy tắc trong thư mục r8. R8 từ phiên bản 8.0.0 trở lên nhưng không bao gồm phiên bản 8.2.0 có thể sử dụng các quy tắc trong thư mục r8-from-8.0.0-upto-8.2.0.
Trình bổ trợ Android cho Gradle sử dụng thông tin đó để chọn tất cả các quy tắc mà phiên bản R8 hiện tại có thể sử dụng. Nếu một thư viện không chỉ định các quy tắc R8
được nhắm mục tiêu, thì Trình bổ trợ Android cho Gradle sẽ chọn các quy tắc từ các vị trí cũ
(proguard.txt cho AAR hoặc META-INF/proguard/<ProGuard-rule-files> cho
JAR).