Jetpack Keep cho phép bạn khai báo việc sử dụng phản chiếu và Giao diện gốc của Java (JNI) bằng chú thích trực tiếp trong mã nguồn Kotlin hoặc Java, thay vì trong các tệp quy tắc giữ riêng biệt. Trình bổ trợ Jetpack Keep Gradle đọc các chú thích này tại thời điểm xây dựng và tạo các quy tắc giữ lại mà R8 cần.
Jetpack Keep giúp tính năng tối ưu hoá R8 duy trì hiệu quả khi cơ sở mã của bạn thay đổi:
Các quy tắc giữ lại nằm bên cạnh mã cần đến chúng. Khi đổi tên hoặc xoá mã sử dụng 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 là 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 nó 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 là trường hợp 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.
Công cụ tìm lỗi mã nguồn sẽ tìm thấy phản chiếu cho bạn. Thư viện này bao gồm các quy trình 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 tạo thực thể cho một lớp theo tên. Nếu R8 không thể xác định rằng mã của bạn sử dụng lớp này, 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 chặn sự cố, 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 chú thích hàm thực hiện 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 giữ lại 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 là quy tắc 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ý phép biế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à kiểm tra tìm lỗi mã nguồn, còn trình bổ trợ sẽ chuyển chú thích thành quy tắc giữ lại.
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ợ có cùng 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.ktscủa bạn có chứagoogle().Trong tệp
build.gradle.ktsgốc, hãy khai báo trình bổ trợ cùng với AGP mà không á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 các tệp Gradle.
Đối với mỗi biến thể bản dựng của mô-đun ứng dụng Android, trình bổ trợ sẽ đăng ký một tác vụ <variant>ExtractKeepRules, chẳng hạn như releaseExtractKeepRules, và chuyển các quy tắc đã tạo đến 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 giữ lại của bên sử dụng thư viện trong kết quả của thư viện.
Các mô-đun thư viện JVM cần được thiết lập thêm.
Tìm phản chiếu bằng lint
Jetpack Keep bao gồm các quy trình 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àgetDeclaredField, không có chú thích giữ lại 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 quy tắc giữ lại 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 trình tìm lỗi mã nguồn (lint) 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à lint có thể tự động thực hiện:
./gradlew :app:lintFixLint 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 lại từng chú thích đã thêm để xác nhận rằng chú thích đó khớp với chức năng của mã. Ví dụ: hãy kiểm tra để đảm bảo 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 lint 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 dưới dạng tham số, vì vậy, lint 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 một chú thích có 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 này có sẵn tại thời gian 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().Hãy kiểm tra xem trình bổ trợ tạo ra những quy tắc giữ lại nào từ chú thích của bạn 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ợ này ghi các quy tắc vào
app/build/generated/release/androidx.annotation.keep.rules.pro. Đối với mô-đun thư viện, hãy xem phần Xác minh các quy tắc đã tạo.Xoá các quy tắc giữ lại mà chú thích mới thay thế khỏi tệp quy tắc giữ lại. Một quy tắc cũng có thể bao gồm phản chiếu mà lint không gắn cờ, chẳng hạn như phản chiếu trong một mô-đun khác hoặc trong thư viện, vì vậy, hãy kiểm tra từng quy tắc trước khi bạn 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 quy tắc khác bao gồm tất cả phản chiếu trên các lớp của gói. - Đối với quy tắc giữ lại 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ính xác với một trong các quy tắc được tạo hoặc không có phản ánh 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 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 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 một chú thích dựa trên chức năng của mã:
| Annotation | Sử dụng khi mã của bạn |
|---|---|
@UsesReflectionToConstruct |
Gọi hàm khởi tạo thông qua phản chiếu, ví dụ: bằng getDeclaredConstructor().newInstance(). |
@UsesReflectionToAccessMethod |
Gọi một phương thức thông qua cơ chế phản chiếu, ví dụ: bằng getMethod hoặc getDeclaredMethod. |
@UsesReflectionToAccessField |
Đọc hoặc ghi một trường thông qua phản chiếu, ví dụ: bằng getField hoặc getDeclaredField. |
@UnconditionallyKeep |
Được gọi từ bên ngoài mã Kotlin hoặc Java của ứng dụng, ví dụ:
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 chú thích @UsesReflection*, vốn tạo ra
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 này 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 tệp 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 tệp tham chiếu đó. Chỉ sử dụngclassNamekhi lớp không có sẵn tại thời gian biên dịch, chẳng hạn như một 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 viên phù hợp trong lớp mà bạn chỉ định và trong các lớp con hoặc cách 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ữ một phương thức nạp chồng duy nhất. Nếu bạn không đặt
parameterTypeshoặcparameterTypeNames, thì các quy tắc sẽ giữ lại tất cả hàm khởi tạo hoặc tất cả phương thức nạp chồng. Để 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 mỗi mục tiêu.
Tạo đối tượng bằng tính năng phản chiếu
Sử dụng @UsesReflectionToConstruct khi mã của bạn tạo thực thể cho một lớp thông qua
phản chiếu. Trong ví dụ sau, trình chạy tác vụ tạo thực thể cho các phương thức 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 phương thức 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 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
phản chiếu. Đặt methodName thành tên của phương thức. Trong ví dụ sau, mã gọi một phương thức tĩnh trong một phần phụ thuộc không bắt buộc. Lớp này không có sẵn tại thời gian biên dịch, vì vậy, chú thích 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 tính năng 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 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ư một phương thức mà mã gốc gọi thông qua JNI. Chú thích phần tử được gọi, chứ 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 đó, cùng 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. Nếu mã gốc phụ thuộc vào các lớp đó, hãy chú thích chúng hoặc thêm quy tắc giữ lại cho chúng. Để biết thêm thông tin về JNI và R8, hãy xem bài viết Tương tác với mã gốc thông qua JNI.
Theo mặc định, @UnconditionallyKeep giữ nguyên tên của phần tử. Nếu phương thức 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 đã 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 đến 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 giữ lại của bên sử dụng thư viện trong kết quả 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 giữ lại 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 xem trình bổ trợ tạo ra quy tắc giữ lại nào cho một mô-đun ứng dụng, hãy chạy tác vụ trích xuất cho một biến thể bản dựng:
./gradlew :app:releaseExtractKeepRules
Trình bổ trợ này 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 xác định mã được chú thích đã tạo ra quy tắc đó.
Để kiểm tra xem các quy tắc được tạo ảnh hưởng như thế nào đến việc tối ưu hoá cùng với các quy tắc giữ lại 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 đã tạo vào tệp proguard.txt của AAR, sau
mọi quy tắc lưu giữ của người dùng mà AAR đã chứa. Trình bổ trợ này đăng ký một tác vụ <variant>KeepRulesTransformAar cho mỗi 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, ví dụ: bằng ./gradlew
:mylibrary:assembleRelease và mở proguard.txt trong AAR đã tạo. Các quy tắc được tạo sẽ xuất hiện trong một phần có tiêu đề như 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 đã 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ợ này đăng ký một 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 đã tạo vào một bản sao của tệp JAR. Sau đó, hãy chạy tác vụ biến đổi mà bạn đã đăng ký và kiểm tra các tệp quy tắc trong tệp JAR mà tác vụ đó tạo ra. Ví dụ: nếu đặt tên cho nhiệm 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 tệp JAR, trình bổ trợ sẽ không tự động sửa đổi tệp JAR. Thay vào đó, hãy sử dụng hàm annotationKeep.registerJavaArchiveTransform để đăng ký một tác vụ tạo bản sao của tệp JAR bằng các quy tắc đã tạo, sau đó phát hành tệp 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 biến đổi sẽ thêm các quy tắ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 tệp JAR đã chứa các quy tắc trong META-INF/com.android.tools/ hoặc META-INF/proguard/, thì phép biến đổi sẽ thêm các quy tắ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 bài viết Hỗ trợ nhiều phiên bản R8.
Sau khi bạn đăng ký phép biế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
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ụ: một SDK phân tích có thể cung cấp một thư viện chính và một tiện ích bổ sung video. Tiện ích bổ sung phụ thuộc vào thư viện chính, vì vậy, thư viện chính 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 chính sẽ tra cứu 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 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 tra cứu 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ó bao gồm 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ẽ cung cấp quy tắc giữ lại của người dùng cho lớp của tiện ích, như minh hoạ trong phần 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 quy tắc tra cứu cần đến nó, vì vậy, hai quy tắc này có thể không đồng bộ. Với Jetpack Keep, bạn chú thích hoạt động tra cứu 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 này tuân theo các nguyên tắc sau:
- Chú thích mã thực hiện phản chiếu. Chú thích nằm trên
initialize, gọiClass.forName, chứ không phải trên lớp của tiện ích bổ sung. - Chọn chú thích dựa trên chức năng của mã. Thao tác tra cứu sẽ tạo một thực thể của lớp, vì vậy, mã sẽ sử dụng
@UsesReflectionToConstruct. - Sử dụng
classNamecho một 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 chính, 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. Thao tác tra cứu sẽ 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ố 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 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ó những ưu điểm sau:
- Quy tắc này nằm bên cạnh hàm tra cứu. Thư viện cốt lõi sở hữu cả tra cứu và quy tắc, vì vậy, bạn cập nhật chúng 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 một ứ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 giữ 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 phát hiện các lượt tra cứu không có chú thích. Nếu sau này thư viện chính thêm một lượt tra cứu phản chiếu khác mà không có chú thích, thì lint sẽ gắn cờ để 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 quy tắc được tạo xuất hiện trong tệp proguard.txt của AAR, như mô tả trong phần Thư viện Android. Sau đó, hãy kiểm thử bản phát hành của một ứ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 số liệu phân tích video.
Các điểm hạn chế
Jetpack Keep chưa phát hiện được phản chiếu tìm thấy các lớp và thành phần theo chú thích của chúng, hoặc tạo quy tắc giữ lại cho phản chiếu đó. Tính năng này đã được lên kế hoạch.
Cho đến lúc đó, hãy sử dụng quy tắc giữ lại cho loại phản chiếu này. Ví dụ: quy tắc sau đây giữ lại mọi thành phần được chú thích bằng @SomeAnnotation trong các lớp mà R8 giữ lại:
-keepclassmembers class * {
@com.example.annotation.SomeAnnotation <methods>;
@com.example.annotation.SomeAnnotation <fields>;
}
Nếu Jetpack Keep không phù hợp với trường hợp sử dụng của bạn hoặc nếu bạn có ý kiến phản hồi khác, hãy gửi vấn đề.
Tài nguyên khác
- Mẫu Jetpack Keep: Một dự án Android Studio sử dụng Jetpack Keep trong một ứng dụng, một thư viện Android, một thư viện Kotlin Multiplatform và một thư viện JVM.
- Giữ lại các trường hợp sử dụng và ví dụ về quy tắc: Giữ lại các quy tắc 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 viết quy tắc giữ lại có phạm vi hẹp.