Jetpack Keep cho phép bạn khai báo việc sử dụng cơ chế phản chiếu và Giao diện gốc Java (JNI) bằng chú thích ngay trong mã nguồn Kotlin hoặc Java, thay vì trong các tệp quy tắc lưu giữ riêng biệt. Trình bổ trợ Jetpack Keep Gradle sẽ đọc các chú thích này tại thời điểm tạo và tạo các quy tắc lưu giữ mà R8 cần.
Jetpack Keep giúp việc tối ưu hoá R8 luôn hiệu quả khi toàn bộ mã nguồn của bạn thay đổi:
Các quy tắc lưu giữ nằm cạnh mã cần đến các quy tắc đó. Khi đổi tên hoặc xoá mã sử dụng cơ chế phản chiếu, bạn sẽ cập nhật chú thích ở cùng một vị trí. Không có tệp quy tắc riêng biệt nào cần đồng bộ hoá.
Các quy tắc được tạo có điều kiện. R8 chỉ giữ lại mục tiêu của lệnh gọi phản chiếu nếu R8 cũng giữ lại mã thực hiện lệnh gọi đó. Nếu mã gọi không được sử dụng và R8 xoá mã đó (thường xảy ra trong các tính năng thư viện không bắt buộc), thì R8 cũng có thể xoá mục tiêu đó. Vì vậy, các quy tắc từ các tính năng không dùng đến sẽ không làm tăng kích thước ứng dụng.
Công cụ tìm lỗi mã nguồn sẽ tìm cơ chế phản chiếu cho bạn. Thư viện này bao gồm các lượt kiểm tra tìm lỗi mã nguồn gắn cờ các lệnh gọi phản chiếu không được chú thích và có thể tự động thêm nhiều chú thích.
Cách hoạt động của Jetpack Keep
Hãy xem xét một hàm khởi tạo một lớp theo tên. Nếu R8 không thể biết rằng mã của bạn sử dụng lớp đó, thì R8 sẽ xoá hàm khởi tạo của lớp đó và ứng dụng sẽ gặp sự cố trong thời gian chạy. Để ngăn sự cố này, bạn có thể thêm một quy tắc như sau vào tệp quy tắc lưu giữ:
# Unconditional: R8 keeps this constructor even if it removes the reflection that invokes it.
-keep class com.example.ResourceLocatorImpl {
<init>();
}
Với Jetpack Keep, bạn sẽ chú thích hàm thực hiện cơ chế phản chiếu:
// ResourceLocatorImpl isn't on this module's compile classpath, so both the
// annotation and the code refer to it by name.
@UsesReflectionToConstruct(
className = "com.example.ResourceLocatorImpl",
// Omitting parameterTypes keeps every constructor. An empty array keeps
// only the no-argument constructor, which the code calls.
parameterTypes = [],
)
fun loadResourceLocator(): ResourceLocator {
val implClass = Class.forName("com.example.ResourceLocatorImpl")
return implClass.getDeclaredConstructor().newInstance() as ResourceLocator
}
Tại thời điểm tạo, trình bổ trợ Gradle sẽ tạo một quy tắc lưu giữ cho hàm khởi tạo.
Không giống như quy tắc được viết thủ công trước đó, quy tắc được tạo có điều kiện: R8 chỉ giữ lại hàm khởi tạo nếu loadResourceLocator vẫn còn trong ứng dụng được tối ưu hoá.
Yêu cầu
Để sử dụng Jetpack Keep, dự án của bạn cần có Trình bổ trợ Android cho Gradle (AGP) 8.7 trở lên.
Trình bổ trợ Gradle hỗ trợ các loại mô-đun sau:
| Loại mô-đun | Trình bổ trợ mà mô-đun áp dụng | Thiết lập bổ sung |
|---|---|---|
| Ứng dụng Android | com.android.application |
Không có |
| Thư viện Android | com.android.library |
Không có |
| Thư viện Kotlin Multiplatform có mục tiêu Android | com.android.kotlin.multiplatform.library |
Không có |
| Thư viện JVM (Java hoặc Kotlin) | java-library hoặc org.jetbrains.kotlin.jvm |
Đăng ký chuyển đổi JAR |
Thiết lập Jetpack Keep
Để sử dụng Jetpack Keep, hãy thêm cả thư viện và trình bổ trợ Gradle vào từng mô-đun sử dụng cơ chế phản chiếu hoặc JNI. Thư viện cung cấp các chú thích và lượt kiểm tra tìm lỗi mã nguồn, đồng thời trình bổ trợ sẽ chuyển các chú thích thành quy tắc lưu giữ.
Trong tệp
libs.versions.toml, hãy thêm thư viện và trình bổ trợ. Thư viện và trình bổ trợ dùng chung phiên bản:[versions] # The library and the plugin ship together. Use one version for both so # that they stay in sync when you upgrade. annotationKeep = "1.0.0-alpha01" [libraries] androidx-annotation-keep = { group = "androidx.annotation.keep", name = "keep", version.ref = "annotationKeep" } [plugins] androidx-annotation-keep = { id = "androidx.annotation.keep", version.ref = "annotationKeep" }Gradle tải trình bổ trợ xuống từ kho lưu trữ Maven của Google. Vì vậy, hãy đảm bảo rằng các kho lưu trữ
pluginManagementtrong tệpsettings.gradle.ktsbao gồmgoogle().Trong tệp
build.gradle.ktsgốc, hãy khai báo trình bổ trợ cùng với AGP mà không cần áp dụng trình bổ trợ đó:plugins { alias(libs.plugins.android.application) apply false // Other plugins. // The plugin relies on AGP classes that it doesn't bundle, so declare // it next to AGP. Declaring it here also gives every module the same // plugin version. alias(libs.plugins.androidx.annotation.keep) apply false }Trong tệp
build.gradle.ktscủa từng mô-đun sử dụng cơ chế phản chiếu hoặc JNI, hãy áp dụng trình bổ trợ và thêm phần phụ thuộc thư viện:plugins { alias(libs.plugins.android.application) // The plugin reads annotations only from this module's own code, so // apply it in every module that uses Jetpack Keep. alias(libs.plugins.androidx.annotation.keep) } dependencies { implementation(libs.androidx.annotation.keep) }Đồng bộ hoá dự án với tệp Gradle.
Đối với từng biến thể bản dựng của mô-đun ứng dụng Android, trình bổ trợ sẽ đăng ký một
<variant>ExtractKeepRules tác vụ, chẳng hạn như releaseExtractKeepRules và truyền
các quy tắc được tạo cho R8 cho biến thể đó. Đối với các mô-đun thư viện, trình bổ trợ sẽ đóng gói các quy tắc được tạo dưới dạng quy tắc lưu giữ của bên sử dụng thư viện trong đầu ra của thư viện.
Các mô-đun thư viện JVM cần thiết lập thêm.
Tìm cơ chế phản chiếu bằng công cụ tìm lỗi mã nguồn
Jetpack Keep bao gồm các lượt kiểm tra tìm lỗi mã nguồn sau:
ReflectionWithoutKeepAnnotations- Gắn cờ các lệnh gọi phản chiếu, chẳng hạn như
Class.forName,getDeclaredConstructor,getDeclaredMethodvàgetDeclaredFieldkhông có chú thích lưu giữ phù hợp. MissingKeepAnnotationPlugin- Gắn cờ các mô-đun phụ thuộc vào
androidx.annotation.keep:keepnhưng không áp dụng trình bổ trợ Gradleandroidx.annotation.keep. Nếu không có trình bổ trợ, bản dựng của bạn sẽ không tạo các quy tắc lưu giữ từ các chú thích.
Để sử dụng công cụ tìm lỗi mã nguồn nhằm chú thích một mô-đun Android, hãy làm theo các bước sau:
Chạy công cụ tìm lỗi mã nguồn trên mô-đun để tìm các lệnh gọi phản chiếu cần chú thích:
./gradlew :app:lintÁp dụng các bản sửa lỗi mà công cụ tìm lỗi mã nguồn có thể tự động thực hiện:
./gradlew :app:lintFixCông cụ tìm lỗi mã nguồn có thể tự động thêm chú thích khi có thể xác định lớp và thành phần mà mã phản ánh.
Xem xét từng chú thích đã thêm để xác nhận rằng chú thích đó khớp với những gì mã của bạn thực hiện. Ví dụ: hãy kiểm tra để đảm bảo rằng các loại tham số khớp với hàm khởi tạo hoặc phương thức mà mã của bạn gọi.
Giải quyết các kết quả còn lại trong Android Studio. Khi công cụ tìm lỗi mã nguồn không thể xác định lớp mục tiêu, bản sửa lỗi nhanh sẽ chèn chú thích bằng phần giữ chỗ
TODOđể bạn hoàn tất. Bạn cũng có thể thêm chú thích theo cách thủ công.Ví dụ: hàm sau đây nhận tên lớp làm tham số, vì vậy, công cụ tìm lỗi mã nguồn không thể xác định lớp mà hàm này tạo. Bản sửa lỗi nhanh sẽ thêm chú thích bằng phần giữ chỗ:
@UsesReflectionToConstruct( className = TODO(), parameterTypes = [] ) fun createInstance(className: String): Any = Class.forName(className).getDeclaredConstructor().newInstance()Thay thế phần giữ chỗ bằng lớp mà mã của bạn tạo. Nếu lớp có sẵn tại thời điểm biên dịch, hãy sử dụng
classConstantthay vìclassName. Trong Kotlin, mã của bạn sẽ không biên dịch cho đến khi bạn thay thếTODO().Kiểm tra các quy tắc lưu giữ mà trình bổ trợ tạo từ các chú thích trước khi bạn xoá bất kỳ quy tắc hiện có nào. Đối với mô-đun ứng dụng, hãy chạy tác vụ trích xuất cho biến thể phát hành:
./gradlew :app:releaseExtractKeepRulesTrình bổ trợ sẽ ghi các quy tắc vào
app/build/generated/release/androidx.annotation.keep.rules.pro. Đối với các mô-đun thư viện, hãy xem phần Xác minh các quy tắc được tạo.Xoá các quy tắc lưu giữ mà các chú thích mới thay thế khỏi tệp quy tắc lưu giữ. Một quy tắc cũng có thể bao gồm cơ chế phản chiếu mà công cụ tìm lỗi mã nguồn không gắn cờ, chẳng hạn như cơ chế phản chiếu trong một mô-đun khác hoặc trong một thư viện. Vì vậy, hãy kiểm tra từng quy tắc trước khi xoá quy tắc đó:
- Đối với quy tắc trên toàn gói, chẳng hạn như
-keep class com.example.** { *; }, hãy đảm bảo rằng các chú thích hoặc các quy tắc khác bao gồm tất cả cơ chế phản chiếu trên các lớp của gói. - Đối với quy tắc lưu giữ các lớp hoặc thành phần cụ thể, hãy đảm bảo rằng quy tắc đó khớp chặt chẽ với một trong các quy tắc được tạo hoặc không có cơ chế phản chiếu nào khác phụ thuộc vào quy tắc đó.
- Đối với quy tắc trên toàn gói, chẳng hạn như
Kiểm thử bản phát hành.
Chú thích mã
Thêm chú thích Jetpack Keep vào mã thực hiện cơ chế phản chiếu: hàm, hàm khởi tạo, trường hoặc lớp chứa lệnh gọi phản chiếu. Chú thích mô tả mục tiêu của cơ chế phản chiếu và quy tắc được tạo chỉ giữ lại mục tiêu đó nếu R8 giữ lại mã được chú thích.
Chọn chú thích dựa trên những gì mã của bạn thực hiện:
| Annotation | Sử dụng khi mã của bạn |
|---|---|
@UsesReflectionToConstruct |
Gọi hàm khởi tạo thông qua cơ chế phản chiếu, chẳng hạn như với
getDeclaredConstructor().newInstance(). |
@UsesReflectionToAccessMethod |
Gọi một phương thức thông qua cơ chế phản chiếu, chẳng hạn như với
getMethod hoặc getDeclaredMethod. |
@UsesReflectionToAccessField |
Đọc hoặc ghi một trường thông qua cơ chế phản chiếu, chẳng hạn như với
getField hoặc getDeclaredField. |
@UnconditionallyKeep |
Được gọi từ bên ngoài mã Kotlin hoặc Java của ứng dụng, chẳng hạn như
từ mã gốc thông qua JNI. Chỉ sử dụng chú thích này khi bạn không thể
sử dụng các chú thích @UsesReflection*, tạo các quy tắc có điều kiện. |
Tất cả chú thích @UsesReflection* đều tuân theo các quy ước sau:
- Xác định lớp mục tiêu bằng
classConstanthoặcclassName. Nếu lớp có sẵn tại thời gian biên dịch, hãy sử dụngclassConstant. Trình biên dịch sẽ kiểm tra thông tin tham chiếu và các hoạt động tái cấu trúc IDE (chẳng hạn như đổi tên) sẽ cập nhật thông tin đó. Chỉ sử dụngclassNamekhi lớp không có sẵn tại thời điểm biên dịch, chẳng hạn như lớp trong phần phụ thuộc không bắt buộc. Hai tham số này loại trừ lẫn nhau. - Nhắm đến lớp cụ thể nhất. Các quy tắc được tạo sẽ giữ lại các thành phần phù hợp trong lớp mà bạn chỉ định và trong các lớp con hoặc quá trình triển khai giao diện của lớp đó. Lớp càng chung chung thì R8 càng giữ lại nhiều mã.
- Chỉ định các loại tham số để giữ lại một lần nạp chồng duy nhất. Nếu bạn không đặt
parameterTypeshoặcparameterTypeNames, các quy tắc sẽ giữ lại tất cả hàm khởi tạo hoặc tất cả lần nạp chồng của phương thức. Để chỉ giữ lại hàm khởi tạo hoặc phương thức không có đối số, hãy truyền một mảng trống. - Lặp lại chú thích cho nhiều mục tiêu. Nếu một hàm phản ánh trên nhiều lớp hoặc thành phần, hãy thêm một chú thích cho từng mục tiêu.
Tạo đối tượng bằng cơ chế phản chiếu
Sử dụng @UsesReflectionToConstruct khi mã của bạn khởi tạo một lớp thông qua cơ chế phản chiếu. Trong ví dụ sau, trình chạy tác vụ sẽ khởi tạo quá trình triển khai giao diện StartupTask theo tên. Vì chú thích chỉ định giao diện, nên các quy tắc được tạo sẽ giữ lại hàm khởi tạo không có đối số của mọi quá trình triển khai StartupTask, miễn là R8 giữ lại execute:
interface StartupTask {
fun run()
}
object TaskRunner {
// The task class is known only at runtime, so target the interface that
// every task implements.
@UsesReflectionToConstruct(classConstant = StartupTask::class, parameterTypes = [])
fun execute(className: String) {
val task = Class.forName(className)
.getDeclaredConstructor()
.newInstance() as StartupTask
task.run()
}
}
Gọi phương thức bằng cơ chế phản chiếu
Sử dụng @UsesReflectionToAccessMethod khi mã của bạn gọi một phương thức thông qua cơ chế phản chiếu. Đặt methodName thành tên của phương thức. Trong ví dụ sau, mã sẽ gọi một phương thức tĩnh trong phần phụ thuộc không bắt buộc. Lớp không có sẵn tại thời gian biên dịch, vì vậy, chú giải sẽ sử dụng className thay vì classConstant:
object CrashReporting {
@UsesReflectionToAccessMethod(
className = "com.example.crashlib.CrashReporter",
methodName = "log",
// Omitting parameterTypes keeps every log overload. Keep only the one
// that the code calls.
parameterTypes = [String::class],
)
fun log(message: String) {
val reporterClass = try {
Class.forName("com.example.crashlib.CrashReporter")
} catch (e: ClassNotFoundException) {
// Crash reporting is optional, so skip it instead of failing.
return
}
reporterClass.getMethod("log", String::class.java).invoke(null, message)
}
}
Truy cập vào các trường bằng cơ chế phản chiếu
Sử dụng @UsesReflectionToAccessField khi mã của bạn đọc hoặc ghi một trường thông qua cơ chế phản chiếu. Đặt fieldName thành tên của trường:
class UserProfileFetcher {
// User doesn't expose internalId, so reading it requires reflection.
@UsesReflectionToAccessField(classConstant = User::class, fieldName = "internalId")
fun readInternalId(user: User): Any? {
val field = User::class.java.getDeclaredField("internalId")
field.isAccessible = true
return field.get(user)
}
}
Giữ lại mã được gọi từ bên ngoài ứng dụng
Sử dụng @UnconditionallyKeep cho mã được gọi từ bên ngoài mã Kotlin hoặc Java của ứng dụng, chẳng hạn như phương thức mà mã gốc gọi thông qua JNI. Chú thích phần tử được gọi, không phải mã gọi phần tử đó:
class NativeBridge {
// Only native code calls this method, through a JNI lookup by name that
// R8 can't see. Keep the method and its name unconditionally.
@UnconditionallyKeep
fun onNativeEvent(message: String) {
Log.d("NativeBridge", "Received from native code: $message")
}
// The default proguard-android-optimize.txt file already keeps native
// method names, so this method doesn't need an annotation.
external fun startNativeProcess()
}
Quy tắc được tạo sẽ giữ lại phương thức được chú thích và lớp chứa phương thức đó, với tên ban đầu. Quy tắc này không giữ lại các lớp khác mà chữ ký của phương thức tham chiếu. Nếu mã gốc phụ thuộc vào các lớp đó, hãy chú thích các lớp đó hoặc thêm quy tắc lưu giữ cho các lớp đó. Để biết thêm thông tin về JNI và R8, hãy xem Tương tác với mã gốc thông qua JNI.
Theo mặc định, @UnconditionallyKeep sẽ giữ lại tên của phần tử. Nếu trình gọi bên ngoài không phụ thuộc vào tên, hãy đặt shouldPreserveName = false để R8 có thể đổi tên phần tử.
Xác minh các quy tắc được tạo
Phần này giải thích cách kiểm tra các quy tắc được tạo cho từng loại mô-đun. Đối với mô-đun ứng dụng, trình bổ trợ sẽ truyền trực tiếp các quy tắc cho R8. Đối với mô-đun thư viện, trình bổ trợ sẽ đóng gói các quy tắc dưới dạng quy tắc lưu giữ của bên sử dụng thư viện trong đầu ra của thư viện. Các ứng dụng phụ thuộc vào thư viện của bạn sẽ tự động áp dụng các quy tắc này khi chạy R8, ngay cả khi các ứng dụng không sử dụng Jetpack Keep. Để biết thêm thông tin về các quy tắc lưu giữ của bên sử dụng thư viện, hãy xem phần Tối ưu hoá cho tác giả thư viện.
Ứng dụng Android
Để kiểm tra các quy tắc lưu giữ mà trình bổ trợ tạo cho mô-đun ứng dụng, hãy chạy tác vụ trích xuất cho biến thể bản dựng:
./gradlew :app:releaseExtractKeepRules
Trình bổ trợ sẽ ghi các quy tắc cho từng biến thể vào
app/build/generated/<variant>/androidx.annotation.keep.rules.pro. Chú thích # context: trước mỗi quy tắc sẽ xác định mã được chú thích đã tạo ra quy tắc đó.
Để kiểm tra cách các quy tắc được tạo ảnh hưởng đến việc tối ưu hoá cùng với các quy tắc lưu giữ khác, hãy sử dụng Trình phân tích cấu hình R8.
Thư viện Android
Các mô-đun thư viện Android không cần thiết lập thêm. Khi bạn tạo AAR, trình bổ trợ sẽ thêm các quy tắc được tạo vào tệp proguard.txt của AAR, sau mọi quy tắc lưu giữ của bên sử dụng thư viện mà AAR đã chứa. Trình bổ trợ sẽ đăng ký tác vụ
<variant>KeepRulesTransformAar cho từng biến thể bản dựng để thực hiện việc này.
Để xác minh các quy tắc, hãy tạo AAR, chẳng hạn như với ./gradlew
:mylibrary:assembleRelease và mở proguard.txt trong AAR được tạo. Các quy tắc được tạo sẽ xuất hiện trong một phần có tiêu đề sau:
#################################################################
# Rules generated from androidx.annotation.keep annotations #
#################################################################
Trên macOS và Linux, bạn cũng có thể in các quy tắc từ dòng lệnh:
unzip -p mylibrary/build/intermediates/aar/release/releaseKeepRulesTransformAar/mylibrary-release.aar proguard.txt
Thư viện Kotlin Multiplatform
Các mô-đun thư viện Kotlin Multiplatform có mục tiêu Android cũng không cần thiết lập thêm. Khi bạn tạo AAR cho mục tiêu Android, trình bổ trợ sẽ thêm các quy tắc được tạo vào tệp proguard.txt của AAR theo cách tương tự như đối với thư viện Android. Trình bổ trợ sẽ đăng ký tác vụ androidMainKeepRulesTransformAar để thực hiện việc này.
Để xác minh các quy tắc, hãy chạy tác vụ đó và mở proguard.txt trong AAR mà tác vụ đó tạo. Trên macOS và Linux, bạn có thể in các quy tắc từ dòng lệnh:
./gradlew :mylibrary:androidMainKeepRulesTransformAar
unzip -p mylibrary/build/intermediates/aar/androidMain/androidMainKeepRulesTransformAar/mylibrary.aar proguard.txt
Thư viện JVM
Trước khi có thể xác minh các quy tắc cho mô-đun thư viện JVM, hãy định cấu hình
mô-đun để trình bổ trợ thêm các quy tắc được tạo vào bản sao
của JAR. Sau đó, hãy chạy tác vụ chuyển đổi mà bạn đã đăng ký và kiểm tra các tệp quy tắc trong JAR mà tác vụ đó tạo. Ví dụ: nếu bạn đặt tên cho tác vụ là keepRulesJar, bạn có thể in các quy tắc trên macOS và Linux bằng các lệnh sau:
./gradlew :mylibrary:keepRulesJar
unzip -p mylibrary/build/intermediates/annotation-keep/keepRulesJar/mylibrary-keep-rules.jar \
"META-INF/com.android.tools/*" "META-INF/proguard/*"
Định cấu hình thư viện JVM
Đối với các mô-đun thư viện JVM tạo ra JAR, trình bổ trợ sẽ không tự động sửa đổi JAR. Thay vào đó, hãy sử dụng hàm annotationKeep.registerJavaArchiveTransform để đăng ký tác vụ tạo bản sao của JAR bằng các quy tắc được tạo, sau đó phát hành JAR đó. Thêm đoạn mã sau vào tệp build.gradle.kts của mô-đun thư viện:
plugins {
id("java-library")
alias(libs.plugins.jetbrains.kotlin.jvm)
alias(libs.plugins.androidx.annotation.keep)
}
val jarTask = tasks.named<Jar>("jar")
// Apps run R8 on this library's code, so the generated rules must be inside
// the JAR. The plugin handles this automatically only for Android modules.
val keepRulesJarTask = annotationKeep.registerJavaArchiveTransform(
taskName = "keepRulesJar",
input = jarTask.flatMap { it.archiveFile },
fileName = jarTask.flatMap { it.archiveFileName }.map { it.replace(".jar", "-keep-rules.jar") },
)
// Consumers of this library resolve apiElements and runtimeElements, so add
// the JAR that contains the generated rules to them.
configurations.configureEach {
if (name == "apiElements" || name == "runtimeElements") {
outgoing.artifact(keepRulesJarTask.flatMap { it.outputJar })
}
}
dependencies {
implementation(libs.androidx.annotation.keep)
}
Quá trình chuyển đổi sẽ thêm các quy tắc được tạo vào vị trí quy tắc R8 được nhắm mục tiêu của JAR, META-INF/com.android.tools/r8/proguard.txt. Nếu JAR đã chứa các quy tắc trong META-INF/com.android.tools/ hoặc META-INF/proguard/, thì quá trình chuyển đổi sẽ nối các quy tắc được tạo vào tệp quy tắc hiện có. Để biết thêm thông tin
về các vị trí này, hãy xem phần Hỗ trợ nhiều phiên bản R8.
Sau khi đăng ký quá trình chuyển đổi, hãy xác minh các quy tắc trong JAR.
Ví dụ: Tải thư viện tiện ích bổ sung không bắt buộc
Các thư viện thường sử dụng cơ chế phản chiếu để tải các tiện ích bổ sung không bắt buộc. Ví dụ: Analytics SDK có thể cung cấp thư viện cốt lõi và tiện ích bổ sung video. Tiện ích bổ sung phụ thuộc vào thư viện cốt lõi, vì vậy, thư viện cốt lõi không thể tham chiếu trực tiếp đến các lớp của tiện ích bổ sung. Thay vào đó, thư viện cốt lõi sẽ tìm lớp của tiện ích bổ sung theo tên trong thời gian chạy và chỉ bật tính năng phân tích video nếu tìm thấy lớp đó.
Vì thư viện cốt lõi chỉ sử dụng lớp của tiện ích bổ sung thông qua cơ chế phản chiếu, nên R8 có thể xoá lớp hoặc hàm khởi tạo của lớp đó khỏi bản phát hành của ứng dụng, khiến quá trình tìm kiếm không thành công. Lỗi này chỉ xảy ra trong các bản phát hành của ứng dụng có chứa tiện ích bổ sung, vì vậy, rất khó phát hiện trong quá trình kiểm thử.
Nếu không có Jetpack Keep, tiện ích bổ sung sẽ vận chuyển quy tắc lưu giữ của bên sử dụng thư viện cho lớp của tiện ích bổ sung, như minh hoạ trong phần Cơ chế phản chiếu để hỗ trợ các phần phụ thuộc không bắt buộc:
# In the add-on's consumer keep rules file.
-keep class com.example.analytics.video.VideoEventTracker {
<init>();
}
Quy tắc này nằm trong một thư viện khác với thư viện tìm kiếm cần đến quy tắc đó, vì vậy, hai thư viện này có thể không đồng bộ. Với Jetpack Keep, bạn sẽ chú thích thư viện tìm kiếm trong thư viện cốt lõi:
// In the core library.
interface AnalyticsPlugin {
fun start()
}
object AnalyticsManager {
// The annotation and the lookup share this constant so that they can't
// refer to different classes.
private const val VIDEO_TRACKER_CLASS =
"com.example.analytics.video.VideoEventTracker"
@UsesReflectionToConstruct(
className = VIDEO_TRACKER_CLASS,
parameterTypes = [],
)
fun initialize() {
val tracker = try {
Class.forName(VIDEO_TRACKER_CLASS)
.getDeclaredConstructor()
.newInstance() as AnalyticsPlugin
} catch (e: ClassNotFoundException) {
// Apps that don't play video leave out the add-on, so a missing
// class isn't an error.
return
}
// This is an ordinary interface call, not a reflective one, so R8
// keeps start() without an annotation.
tracker.start()
}
}
Chú thích tuân theo các nguyên tắc sau:
- Chú thích mã thực hiện cơ chế phản chiếu. Chú thích nằm trên
initialize, gọiClass.forName, không nằm trên lớp của tiện ích bổ sung. - Chọn chú thích dựa trên những gì mã thực hiện. Thư viện tìm kiếm tạo một thực thể của lớp, vì vậy, mã sẽ sử dụng
@UsesReflectionToConstruct. - Sử dụng
classNamecho lớp mà bạn không thể tham chiếu. Lớp của tiện ích bổ sung không nằm trên classpath biên dịch của thư viện cốt lõi, vì vậy, chú thích không thể sử dụngclassConstant. - Chỉ giữ lại hàm khởi tạo mà mã gọi. Thư viện tìm kiếm gọi hàm khởi tạo không có đối số, vì vậy,
parameterTypeslà một mảng trống.
Khi bạn tạo AAR của thư viện cốt lõi, trình bổ trợ sẽ thêm một quy tắc giữ lại VideoEventTracker và hàm khởi tạo không có đối số của thư viện đó bất cứ khi nào R8 giữ lại initialize. Các ứng dụng sử dụng SDK sẽ tự động áp dụng quy tắc này, ngay cả khi các ứng dụng đó không sử dụng Jetpack Keep. Vì vậy, tiện ích bổ sung không cần quy tắc riêng.
So với quy tắc của tiện ích bổ sung, phương pháp này có các ưu điểm sau:
- Quy tắc nằm cạnh thư viện tìm kiếm. Thư viện cốt lõi sở hữu cả thư viện tìm kiếm và quy tắc, vì vậy, bạn sẽ cập nhật cả hai thư viện này cùng nhau thay vì điều phối thay đổi trên hai thư viện.
- R8 vẫn có thể xoá mã không dùng đến. Nếu ứng dụng có chứa tiện ích bổ sung nhưng không bao giờ gọi
initialize, thì quy tắc được tạo sẽ không áp dụng và R8 có thể xoáVideoEventTracker. Quy tắc của tiện ích bổ sung sẽ giữ lại tiện ích bổ sung trong mọi ứng dụng có chứa tiện ích bổ sung đó. - Công cụ tìm lỗi mã nguồn sẽ phát hiện các thư viện tìm kiếm không được chú thích. Nếu sau này thư viện cốt lõi thêm một thư viện tìm kiếm phản chiếu khác mà không có chú thích, thì công cụ tìm lỗi mã nguồn sẽ gắn cờ thư viện đó để bạn có thể khắc phục trước khi phát hành thư viện.
Để xác nhận kết quả, hãy kiểm tra để đảm bảo rằng quy tắc được tạo xuất hiện trong tệp AAR's
proguard.txt, như mô tả trong phần Thư viện Android. Sau đó, hãy kiểm thử bản phát hành của ứng dụng có chứa tiện ích bổ sung, vì đó là nơi quy tắc bị thiếu sẽ làm hỏng tính năng phân tích video.
Tài nguyên khác
- Mẫu Jetpack Keep: Dự án Android Studio sử dụng Jetpack Keep trong ứng dụng, thư viện Android, thư viện Kotlin Multiplatform và thư viện JVM.
- Các trường hợp sử dụng và ví dụ về quy tắc lưu giữ: Các quy tắc lưu giữ cho các tình huống phản chiếu và JNI phổ biến.
- Làm theo các phương pháp hay nhất: Hướng dẫn về cách viết các quy tắc lưu giữ có phạm vi hẹp.