Jetpack Keep memungkinkan Anda mendeklarasikan penggunaan refleksi dan Java Native Interface (JNI) dengan anotasi langsung di kode sumber Kotlin atau Java, bukan di file aturan penyimpanan terpisah. Plugin Gradle Keep Jetpack membaca anotasi ini pada waktu build dan membuat aturan keep yang dibutuhkan R8.
Jetpack Keep membantu pengoptimalan R8 tetap efektif seiring perubahan codebase Anda:
Aturan tetap berada di samping kode yang memerlukannya. Saat mengganti nama atau menghapus kode yang menggunakan refleksi, Anda mengupdate anotasi di tempat yang sama. Tidak ada file aturan terpisah yang harus disinkronkan.
Aturan yang dihasilkan bersifat bersyarat. R8 mempertahankan target panggilan reflektif hanya jika R8 juga mempertahankan kode yang melakukan panggilan. Jika kode panggilan tidak digunakan dan R8 menghapusnya (sering terjadi pada fitur library opsional), R8 juga dapat menghapus target, sehingga aturan dari fitur yang tidak digunakan tidak akan menambah beban.
Lint menemukan refleksi untuk Anda. Library ini mencakup pemeriksaan lint yang menandai panggilan reflektif yang tidak diberi anotasi, dan yang dapat menambahkan banyak anotasi secara otomatis.
Cara kerja Jetpack Keep
Pertimbangkan fungsi yang membuat instance class berdasarkan nama. Jika R8 tidak dapat mengetahui bahwa kode Anda menggunakan class, R8 akan menghapus konstruktor class, dan aplikasi akan error saat runtime. Untuk mencegah error, Anda dapat menambahkan aturan seperti berikut ke file aturan keep:
# Unconditional: R8 keeps this constructor even if it removes the reflection that invokes it.
-keep class com.example.ResourceLocatorImpl {
<init>();
}
Dengan Jetpack Keep, Anda menganotasi fungsi yang melakukan refleksi:
// 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
}
Pada waktu build, plugin Gradle menghasilkan aturan penyimpanan untuk konstruktor.
Tidak seperti aturan yang ditulis secara manual sebelumnya, aturan yang dihasilkan bersifat kondisional:
R8 mempertahankan konstruktor hanya jika loadResourceLocator tetap ada di
aplikasi yang dioptimalkan.
Persyaratan
Untuk menggunakan Jetpack Keep, project Anda memerlukan Plugin Android Gradle (AGP) 8.7 atau yang lebih tinggi.
Plugin Gradle mendukung jenis modul berikut:
| Jenis modul | Plugin yang diterapkan modul | Penyiapan tambahan |
|---|---|---|
| Aplikasi Android | com.android.application |
Tidak ada |
| Library Android | com.android.library |
Tidak ada |
| Library Multiplatform Kotlin dengan target Android | com.android.kotlin.multiplatform.library |
Tidak ada |
| Library JVM (Java atau Kotlin) | java-library atau org.jetbrains.kotlin.jvm |
Mendaftarkan transformasi JAR |
Menyiapkan Jetpack Keep
Untuk menggunakan Jetpack Keep, tambahkan library dan plugin Gradle ke setiap modul yang menggunakan refleksi atau JNI. Library menyediakan anotasi dan pemeriksaan lint, dan plugin mengubah anotasi menjadi aturan penyimpanan.
Dalam file
libs.versions.toml, tambahkan library dan plugin. Library dan plugin memiliki versi yang sama:[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 mendownload plugin dari repositori Maven Google, jadi pastikan repositori
pluginManagementdi filesettings.gradle.ktsAnda mencakupgoogle().Dalam file
build.gradle.ktsroot, deklarasikan plugin bersama AGP tanpa menerapkannya: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 }Dalam file
build.gradle.ktssetiap modul yang menggunakan refleksi atau JNI, terapkan plugin dan tambahkan dependensi library: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) }Sinkronkan project Anda dengan file Gradle.
Untuk setiap varian build modul aplikasi Android, plugin mendaftarkan
tugas <variant>ExtractKeepRules, seperti releaseExtractKeepRules, dan meneruskan
aturan yang dihasilkan ke R8 untuk varian tersebut. Untuk modul library, plugin
memaketkan aturan yang dihasilkan sebagai aturan keep konsumen dalam output library.
Modul library JVM memerlukan penyiapan tambahan.
Menemukan refleksi dengan lint
Jetpack Keep mencakup pemeriksaan lint berikut:
ReflectionWithoutKeepAnnotations- Menandai panggilan reflektif, seperti
Class.forName,getDeclaredConstructor,getDeclaredMethod, dangetDeclaredField, yang tidak memiliki anotasi keep yang cocok. MissingKeepAnnotationPlugin- Menandai modul yang bergantung pada
androidx.annotation.keep:keep, tetapi tidak menerapkan plugin Gradleandroidx.annotation.keep. Tanpa plugin, build Anda tidak akan membuat aturan penyimpanan dari anotasi.
Untuk menggunakan lint guna menganotasi modul Android, ikuti langkah-langkah berikut:
Jalankan lint pada modul untuk menemukan panggilan reflektif yang memerlukan anotasi:
./gradlew :app:lintMenerapkan perbaikan yang dapat dilakukan lint secara otomatis:
./gradlew :app:lintFixLint dapat menambahkan anotasi secara otomatis saat dapat menentukan class dan anggota yang direfleksikan oleh kode.
Tinjau setiap anotasi yang ditambahkan untuk mengonfirmasi bahwa anotasi tersebut cocok dengan fungsi kode Anda. Misalnya, pastikan jenis parameter cocok dengan konstruktor atau metode yang dipanggil kode Anda.
Selesaikan temuan yang tersisa di Android Studio. Jika lint tidak dapat menentukan class target, perbaikan cepat akan menyisipkan anotasi dengan placeholder
TODOuntuk Anda selesaikan. Anda juga dapat menambahkan anotasi secara manual.Misalnya, fungsi berikut menerima nama class sebagai parameter, sehingga lint tidak dapat menentukan class mana yang dibuatnya. Perbaikan cepat menambahkan anotasi dengan placeholder:
@UsesReflectionToConstruct( className = TODO(), parameterTypes = [] ) fun createInstance(className: String): Any = Class.forName(className).getDeclaredConstructor().newInstance()Ganti placeholder dengan class yang dibuat oleh kode Anda. Jika class tersedia pada waktu kompilasi, gunakan
classConstant, bukanclassName. Di Kotlin, kode Anda tidak akan dikompilasi hingga Anda menggantiTODO().Periksa aturan keep yang dihasilkan plugin dari anotasi Anda sebelum menghapus aturan yang ada. Untuk modul aplikasi, jalankan tugas ekstraksi untuk varian rilis:
./gradlew :app:releaseExtractKeepRulesPlugin akan menulis aturan ke
app/build/generated/release/androidx.annotation.keep.rules.pro. Untuk modul pustaka, lihat Memverifikasi aturan yang dihasilkan.Hapus aturan penyimpanan yang digantikan oleh anotasi baru dari file aturan penyimpanan Anda. Aturan juga dapat mencakup refleksi yang tidak ditandai oleh lint, seperti refleksi di modul lain atau di library, jadi periksa setiap aturan sebelum Anda menghapusnya:
- Untuk aturan seluruh paket, seperti
-keep class com.example.** { *; }, pastikan anotasi atau aturan lain mencakup semua refleksi pada class paket. - Untuk aturan yang mempertahankan kelas atau anggota tertentu, pastikan aturan tersebut cocok dengan salah satu aturan yang dihasilkan, atau tidak ada refleksi lain yang bergantung padanya.
- Untuk aturan seluruh paket, seperti
Uji build rilis Anda.
Memberi anotasi pada kode Anda
Tambahkan anotasi Jetpack Keep ke kode yang melakukan refleksi: fungsi, konstruktor, kolom, atau class yang berisi panggilan reflektif. Anotasi menjelaskan target refleksi, dan aturan yang dihasilkan hanya mempertahankan target tersebut jika R8 mempertahankan kode yang dianotasi.
Pilih anotasi berdasarkan fungsi kode Anda:
| Annotation | Gunakan saat kode Anda |
|---|---|
@UsesReflectionToConstruct |
Memanggil konstruktor melalui refleksi, misalnya dengan
getDeclaredConstructor().newInstance(). |
@UsesReflectionToAccessMethod |
Memanggil metode melalui refleksi, misalnya dengan
getMethod atau getDeclaredMethod. |
@UsesReflectionToAccessField |
Membaca atau menulis kolom melalui refleksi, misalnya dengan
getField atau getDeclaredField. |
@UnconditionallyKeep |
Dipanggil dari luar kode Kotlin atau Java aplikasi Anda, misalnya
dari kode native melalui JNI. Gunakan anotasi ini hanya jika Anda tidak dapat
menggunakan anotasi @UsesReflection*, yang menghasilkan
aturan bersyarat. |
Semua anotasi @UsesReflection* mengikuti konvensi berikut:
- Identifikasi class target dengan
classConstantatauclassName. Jika class tersedia pada waktu kompilasi, gunakanclassConstant. Compiler memeriksa referensi, dan pemfaktoran ulang IDE seperti penggantian nama akan memperbaruinya. GunakanclassNamehanya jika class tidak tersedia pada waktu kompilasi, seperti class dalam dependensi opsional. Kedua parameter ini saling terpisah. - Menargetkan kelas yang paling spesifik. Aturan yang dihasilkan akan mempertahankan anggota yang cocok di class yang Anda tentukan dan di subclass atau implementasi antarmukanya. Semakin umum class, semakin banyak kode yang dipertahankan R8.
- Tentukan jenis parameter untuk mempertahankan satu overload. Jika Anda tidak menetapkan
parameterTypesatauparameterTypeNames, aturan akan mempertahankan semua konstruktor atau semua overload metode. Untuk hanya mempertahankan konstruktor atau metode tanpa argumen, teruskan array kosong. - Ulangi anotasi untuk beberapa target. Jika satu fungsi merefleksikan lebih dari satu class atau anggota, tambahkan satu anotasi untuk setiap target.
Membuat objek dengan refleksi
Gunakan @UsesReflectionToConstruct saat kode Anda membuat instance class melalui
refleksi. Dalam contoh berikut, task runner membuat instance
implementasi antarmuka StartupTask berdasarkan nama. Karena anotasi
menentukan antarmuka, aturan yang dihasilkan akan mempertahankan konstruktor tanpa argumen
dari setiap penerapan StartupTask, selama R8 mempertahankan 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()
}
}
Memanggil metode dengan refleksi
Gunakan @UsesReflectionToAccessMethod saat kode Anda memanggil metode melalui
refleksi. Tetapkan methodName ke nama metode. Dalam contoh
berikut, kode memanggil metode statis dalam dependensi opsional. Class tidak tersedia pada waktu kompilasi, sehingga anotasi menggunakan className, bukan 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)
}
}
Mengakses kolom dengan refleksi
Gunakan @UsesReflectionToAccessField saat kode Anda membaca atau menulis kolom
melalui refleksi. Tetapkan fieldName ke nama kolom:
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)
}
}
Menjaga kode yang dipanggil dari luar aplikasi Anda
Gunakan @UnconditionallyKeep untuk kode yang dipanggil dari luar kode Kotlin atau Java aplikasi Anda, seperti metode yang dipanggil kode native melalui JNI. Anotasikan
elemen yang dipanggil, bukan kode yang memanggilnya:
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()
}
Aturan yang dihasilkan akan mempertahankan metode yang dianotasi dan class yang memuatnya, dengan nama aslinya. Aturan ini tidak mempertahankan class lain yang dirujuk oleh tanda tangan metode. Jika kode native bergantung pada class tersebut, beri anotasi juga, atau tambahkan aturan keep untuknya. Untuk mengetahui informasi selengkapnya tentang JNI dan R8, lihat Interaksi dengan kode native melalui JNI.
Secara default, @UnconditionallyKeep mempertahankan nama elemen. Jika pemanggil eksternal tidak bergantung pada nama, tetapkan shouldPreserveName = false agar R8 dapat mengganti nama elemen.
Memverifikasi aturan yang dihasilkan
Bagian ini menjelaskan cara memeriksa aturan yang dihasilkan untuk setiap jenis modul. Untuk modul aplikasi, plugin meneruskan aturan langsung ke R8. Untuk modul library, plugin mengemas aturan sebagai aturan keep konsumen dalam output library. Aplikasi yang bergantung pada library Anda akan menerapkan aturan ini secara otomatis saat menjalankan R8, meskipun aplikasi tersebut tidak menggunakan Jetpack Keep. Untuk informasi selengkapnya tentang aturan keep konsumen, lihat Pengoptimalan untuk penulis library.
Aplikasi Android
Untuk memeriksa aturan keep yang dihasilkan plugin untuk modul aplikasi, jalankan tugas ekstraksi untuk varian build:
./gradlew :app:releaseExtractKeepRules
Plugin menulis aturan untuk setiap varian ke
app/build/generated/<variant>/androidx.annotation.keep.rules.pro. Komentar # context: sebelum setiap aturan mengidentifikasi kode yang dianotasi yang menghasilkannya.
Untuk memeriksa pengaruh aturan yang dihasilkan terhadap pengoptimalan bersama dengan aturan keep lainnya, gunakan R8 Configuration Analyzer.
Library Android
Modul library Android tidak memerlukan penyiapan tambahan. Saat Anda mem-build
AAR, plugin akan menambahkan aturan yang dihasilkan ke file proguard.txt AAR, setelah
aturan keep konsumen yang sudah ada dalam AAR. Plugin mendaftarkan tugas
<variant>KeepRulesTransformAar untuk setiap varian build guna melakukannya.
Untuk memverifikasi aturan, build AAR, misalnya dengan ./gradlew
:mylibrary:assembleRelease, dan buka proguard.txt di AAR yang dihasilkan. Aturan yang dibuat muncul di bagian dengan header berikut:
#################################################################
# Rules generated from androidx.annotation.keep annotations #
#################################################################
Di macOS dan Linux, Anda juga dapat mencetak aturan dari command line:
unzip -p mylibrary/build/intermediates/aar/release/releaseKeepRulesTransformAar/mylibrary-release.aar proguard.txt
Library Multiplatform Kotlin
Modul library Multiplatform Kotlin dengan target Android juga tidak memerlukan penyiapan
tambahan. Saat Anda mem-build AAR untuk target Android, plugin akan menambahkan aturan yang dihasilkan ke file proguard.txt AAR dengan cara yang sama seperti untuk library Android. Plugin mendaftarkan tugas androidMainKeepRulesTransformAar untuk melakukannya.
Untuk memverifikasi aturan, jalankan tugas tersebut dan buka proguard.txt di AAR yang
dibuatnya. Di macOS dan Linux, Anda dapat mencetak aturan dari command line:
./gradlew :mylibrary:androidMainKeepRulesTransformAar
unzip -p mylibrary/build/intermediates/aar/androidMain/androidMainKeepRulesTransformAar/mylibrary.aar proguard.txt
Library JVM
Sebelum Anda dapat memverifikasi aturan untuk modul library JVM, konfigurasikan
modul agar plugin menambahkan aturan yang dihasilkan ke salinan
JAR-nya. Kemudian, jalankan tugas transformasi yang Anda daftarkan dan periksa file aturan
dalam JAR yang dibuatnya. Misalnya, jika Anda memberi nama tugas
keepRulesJar, Anda dapat mencetak aturan di macOS dan Linux dengan perintah
berikut:
./gradlew :mylibrary:keepRulesJar
unzip -p mylibrary/build/intermediates/annotation-keep/keepRulesJar/mylibrary-keep-rules.jar \
"META-INF/com.android.tools/*" "META-INF/proguard/*"
Mengonfigurasi library JVM
Untuk modul library JVM yang menghasilkan JAR, plugin tidak mengubah JAR secara otomatis. Sebagai gantinya, gunakan fungsi annotationKeep.registerJavaArchiveTransform
untuk mendaftarkan tugas yang membuat salinan JAR dengan aturan yang dihasilkan, lalu publikasikan JAR tersebut. Tambahkan kode berikut ke file build.gradle.kts modul library:
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)
}
Transformasi menambahkan aturan yang dihasilkan ke lokasi aturan R8 yang ditargetkan JAR,
META-INF/com.android.tools/r8/proguard.txt. Jika JAR sudah berisi aturan
di META-INF/com.android.tools/ atau META-INF/proguard/, transformasi akan menambahkan
aturan yang dihasilkan ke file aturan yang ada. Untuk informasi selengkapnya
tentang lokasi ini, lihat Mendukung berbagai versi R8.
Setelah mendaftarkan transformasi, verifikasi aturan dalam JAR.
Contoh: Memuat library add-on opsional
Library sering menggunakan refleksi untuk memuat add-on opsional. Misalnya, SDK analisis mungkin mengirimkan library inti dan add-on video. Add-on bergantung pada library inti, sehingga library inti tidak dapat merujuk ke class add-on secara langsung. Sebagai gantinya, library inti mencari class add-on berdasarkan nama saat runtime dan mengaktifkan analisis video hanya jika menemukan class.
Karena library inti hanya menggunakan class add-on melalui refleksi, R8 dapat menghapus class atau konstruktornya dari build rilis aplikasi, yang membuat pencarian gagal. Kegagalan hanya terjadi pada build rilis aplikasi yang menyertakan add-on, sehingga sulit untuk diketahui dalam pengujian.
Tanpa Jetpack Keep, add-on akan mengirimkan aturan keep konsumen untuk class-nya, seperti yang ditunjukkan dalam Refleksi untuk mendukung dependensi opsional:
# In the add-on's consumer keep rules file.
-keep class com.example.analytics.video.VideoEventTracker {
<init>();
}
Aturan ini berada di library yang berbeda dari pencarian yang memerlukannya, sehingga keduanya dapat menjadi tidak sinkron. Dengan Jetpack Keep, Anda dapat membuat anotasi pencarian di library inti:
// 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()
}
}
Anotasi mengikuti panduan berikut:
- Anotasikan kode yang melakukan refleksi. Anotasi ada di
initialize, yang memanggilClass.forName, bukan di class add-on. - Pilih anotasi berdasarkan fungsi kode. Pencarian membuat
instance class, sehingga kode menggunakan
@UsesReflectionToConstruct. - Gunakan
classNameuntuk class yang tidak dapat Anda referensikan. Class add-on tidak ada di classpath kompilasi library inti, sehingga anotasi tidak dapat menggunakanclassConstant. - Hanya simpan konstruktor yang dipanggil kode. Pencarian memanggil
konstruktor tanpa argumen, sehingga
parameterTypesadalah array kosong.
Saat Anda mem-build AAR library inti, plugin akan menambahkan aturan yang mempertahankan
VideoEventTracker dan konstruktor tanpa argumennya setiap kali R8 mempertahankan
initialize. Aplikasi yang menggunakan SDK menerapkan aturan ini secara otomatis, meskipun tidak menggunakan Jetpack Keep, sehingga add-on tidak memerlukan aturannya sendiri.
Dibandingkan dengan aturan add-on, pendekatan ini memiliki keuntungan sebagai berikut:
- Aturan tetap berada di samping pencarian. Library inti memiliki pencarian dan aturan, sehingga Anda dapat mengupdatenya bersama-sama, bukan mengoordinasikan perubahan di dua library.
- R8 tetap dapat menghapus kode yang tidak digunakan. Jika aplikasi menyertakan add-on, tetapi
tidak pernah memanggil
initialize, aturan yang dihasilkan tidak akan berlaku, dan R8 dapat menghapusVideoEventTracker. Aturan add-on menyimpannya di setiap aplikasi yang menyertakan add-on. - Lint menangkap pencarian yang tidak dianotasi. Jika library inti kemudian menambahkan pencarian reflektif lain tanpa anotasi, lint akan menandainya, sehingga Anda dapat memperbaikinya sebelum merilis library.
Untuk mengonfirmasi hasilnya, pastikan aturan yang dihasilkan muncul di file proguard.txt AAR, seperti yang dijelaskan dalam Library Android. Kemudian,
uji build rilis aplikasi yang menyertakan add-on, karena di situlah
aturan yang tidak ada akan merusak analisis video.
Referensi lainnya
- Contoh Jetpack Keep: Project Android Studio yang menggunakan Jetpack Keep di aplikasi, library Android, library Kotlin Multiplatform, dan library JVM.
- Kasus penggunaan dan contoh aturan keep: Aturan keep untuk skenario refleksi dan JNI umum.
- Mengikuti praktik terbaik: Panduan dalam menulis aturan keep dengan cakupan yang sempit.