Jetpack Keep vous permet de déclarer l'utilisation de la réflexion et de l'interface JNI (Java Native Interface) avec des annotations directement dans votre code source Kotlin ou Java, plutôt que dans des fichiers de règles de conservation distincts. Le plug-in Gradle Jetpack Keep lit ces annotations au moment de la compilation et génère les règles de conservation dont R8 a besoin.
Jetpack Keep permet à l'optimisation R8 de rester efficace à mesure que votre codebase évolue :
Les règles de conservation restent à côté du code qui en a besoin. Lorsque vous renommez ou supprimez du code qui utilise la réflexion, vous mettez à jour l'annotation au même endroit. Il n'y a pas de fichier de règles distinct à synchroniser.
Les règles générées sont conditionnelles. R8 ne conserve la cible d'un appel réfléchi que s'il conserve également le code qui effectue l'appel. Si le code d'appel n'est pas utilisé et que R8 le supprime (ce qui est souvent le cas dans les fonctionnalités de bibliothèque facultatives), R8 peut également supprimer la cible, de sorte que les règles des fonctionnalités inutilisées n'ajoutent pas de code superflu.
Lint trouve la réflexion pour vous. La bibliothèque inclut des vérifications lint qui signalent les appels réfléchis non annotés et qui peuvent ajouter automatiquement de nombreuses annotations.
Fonctionnement de Jetpack Keep
Prenons l'exemple d'une fonction qui instancie une classe par son nom. Si R8 ne peut pas déterminer que votre code utilise la classe, il supprime le constructeur de la classe et l'application plante au moment de l'exécution. Pour éviter le plantage, vous pouvez ajouter une règle semblable à la suivante à votre fichier de règles de conservation :
# Unconditional: R8 keeps this constructor even if it removes the reflection that invokes it.
-keep class com.example.ResourceLocatorImpl {
<init>();
}
Avec Jetpack Keep, vous annotez plutôt la fonction qui effectue la réflexion :
// 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
}
Au moment de la compilation, le plug-in Gradle génère une règle de conservation pour le constructeur.
Contrairement à la règle précédente écrite manuellement, la règle générée est conditionnelle : R8 ne conserve le constructeur que si loadResourceLocator reste dans l'application optimisée.
Conditions requises
Pour utiliser Jetpack Keep, votre projet doit utiliser le plug-in Android Gradle (AGP) 8.7 ou une version ultérieure.
Le plug-in Gradle est compatible avec les types de modules suivants :
| Module type | Plug-in appliqué par le module | Configuration supplémentaire |
|---|---|---|
| Application Android | com.android.application |
Aucun |
| Bibliothèque Android | com.android.library |
Aucun |
| Bibliothèque multiplateforme Kotlin avec une cible Android | com.android.kotlin.multiplatform.library |
Aucun |
| Bibliothèque JVM (Java ou Kotlin) | java-library ou org.jetbrains.kotlin.jvm |
Enregistrer une transformation JAR |
Configurer Jetpack Keep
Pour utiliser Jetpack Keep, ajoutez la bibliothèque et le plug-in Gradle à chaque module qui utilise la réflexion ou JNI. La bibliothèque fournit les annotations et les vérifications lint, et le plug-in transforme les annotations en règles de conservation.
Dans votre fichier
libs.versions.toml, ajoutez la bibliothèque et le plug-in. La bibliothèque et le plug-in partagent la même version :[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élécharge le plug-in à partir du dépôt Maven de Google. Assurez-vous donc que les dépôts
pluginManagementde votre fichiersettings.gradle.ktsincluentgoogle().Dans votre fichier
build.gradle.ktsracine, déclarez le plug-in avec AGP sans l'appliquer :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 }Dans le fichier
build.gradle.ktsde chaque module qui utilise la réflexion ou JNI, appliquez le plug-in et ajoutez la dépendance de bibliothèque :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) }Synchronisez votre projet avec les fichiers Gradle.
Pour chaque variante de compilation d'un module d'application Android, le plug-in enregistre une tâche <variant>ExtractKeepRules, telle que releaseExtractKeepRules, et transmet les règles générées à R8 pour cette variante. Pour les modules de bibliothèque, le plug-in empaquette les règles générées en tant que règles de conservation des consommateurs dans la sortie de la bibliothèque.
Les modules de bibliothèque JVM nécessitent une configuration supplémentaire.
Trouver une réflexion avec lint
Jetpack Keep inclut les vérifications lint suivantes :
ReflectionWithoutKeepAnnotations- Signale les appels réfléchis, tels que
Class.forName,getDeclaredConstructor,getDeclaredMethodetgetDeclaredField, qui n'ont pas d'annotation de conservation correspondante. MissingKeepAnnotationPlugin- Signale les modules qui dépendent de
androidx.annotation.keep:keep, mais n'appliquent pas le plug-in Gradleandroidx.annotation.keep. Sans le plug-in, votre build ne génère pas de règles de conservation à partir des annotations.
Pour utiliser lint afin d'annoter un module Android, procédez comme suit :
Exécutez lint sur le module pour trouver les appels de réflexion qui nécessitent des annotations :
./gradlew :app:lintAppliquez les corrections que lint peut effectuer automatiquement :
./gradlew :app:lintFixLint peut ajouter une annotation automatiquement lorsqu'il peut déterminer la classe et les membres sur lesquels le code réfléchit.
Examinez chaque annotation ajoutée pour vérifier qu'elle correspond à ce que fait votre code. Par exemple, vérifiez que les types de paramètres correspondent au constructeur ou à la méthode que votre code appelle.
Résolvez les problèmes restants dans Android Studio. Lorsque lint ne peut pas déterminer la classe cible, la correction rapide insère l'annotation avec un espace réservé
TODOque vous devez compléter. Vous pouvez également ajouter l'annotation manuellement.Par exemple, la fonction suivante reçoit le nom de la classe en tant que paramètre. Lint ne peut donc pas déterminer la classe qu'elle construit. La correction rapide ajoute une annotation avec un espace réservé :
@UsesReflectionToConstruct( className = TODO(), parameterTypes = [] ) fun createInstance(className: String): Any = Class.forName(className).getDeclaredConstructor().newInstance()Remplacez l'espace réservé par la classe construite par votre code. Si la classe est disponible au moment de la compilation, utilisez
classConstantau lieu declassName. En Kotlin, votre code ne se compile pas tant que vous n'avez pas remplacéTODO().Vérifiez les règles de conservation générées par le plug-in à partir de vos annotations avant de supprimer les règles existantes. Pour un module d'application, exécutez la tâche d'extraction pour la variante de release :
./gradlew :app:releaseExtractKeepRulesLe plug-in écrit les règles dans
app/build/generated/release/androidx.annotation.keep.rules.pro. Pour les modules de bibliothèque, consultez Vérifier les règles générées.Supprimez les règles de conservation que les nouvelles annotations remplacent de vos fichiers de règles de conservation. Une règle peut également couvrir une réflexion que lint n'a pas signalée, par exemple une réflexion dans un autre module ou dans une bibliothèque. Vérifiez donc chaque règle avant de la supprimer :
- Pour une règle à l'échelle du package, telle que
-keep class com.example.** { *; }, assurez-vous que les annotations ou d'autres règles couvrent toutes les réflexions sur les classes du package. - Pour une règle qui conserve des classes ou des membres spécifiques, assurez-vous qu'elle correspond étroitement à l'une des règles générées ou qu'aucune autre réflexion n'en dépend.
- Pour une règle à l'échelle du package, telle que
Testez votre build.
Annoter votre code
Ajoutez une annotation Jetpack Keep au code qui effectue la réflexion : la fonction, le constructeur, le champ ou la classe qui contient l'appel de réflexion. L'annotation décrit la cible de la réflexion, et la règle générée ne conserve cette cible que si R8 conserve le code annoté.
Choisissez une annotation en fonction de ce que fait votre code :
| Annotation | Utilisez-le lorsque votre code |
|---|---|
@UsesReflectionToConstruct |
Appelle un constructeur par réflexion, par exemple avec getDeclaredConstructor().newInstance(). |
@UsesReflectionToAccessMethod |
Appelle une méthode par réflexion, par exemple avec getMethod ou getDeclaredMethod. |
@UsesReflectionToAccessField |
Lit ou écrit un champ par réflexion, par exemple avec getField ou getDeclaredField. |
@UnconditionallyKeep |
Elle est appelée depuis l'extérieur du code Kotlin ou Java de votre application, par exemple depuis du code natif via JNI. N'utilisez cette annotation que lorsque vous ne pouvez pas utiliser les annotations @UsesReflection*, qui génèrent des règles conditionnelles. |
Toutes les annotations @UsesReflection* respectent les conventions suivantes :
- Identifiez la classe cible avec
classConstantouclassName. Si la classe est disponible au moment de la compilation, utilisezclassConstant. Le compilateur vérifie la référence, et les refactorisations de l'IDE, telles que le changement de nom, la mettent à jour. N'utilisezclassNameque lorsque la classe n'est pas disponible au moment de la compilation, par exemple une classe dans une dépendance facultative. Ces deux paramètres s'excluent mutuellement. - Ciblez la classe la plus spécifique. Les règles générées conservent les membres correspondants dans la classe que vous spécifiez et dans ses sous-classes ou implémentations d'interface. Plus la classe est générale, plus R8 conserve de code.
- Spécifiez les types de paramètres pour ne conserver qu'une seule surcharge. Si vous ne définissez pas
parameterTypesouparameterTypeNames, les règles conservent tous les constructeurs ou toutes les surcharges de la méthode. Pour ne conserver que le constructeur ou la méthode sans argument, transmettez un tableau vide. - Répétez l'annotation pour plusieurs cibles. Si une fonction reflète plusieurs classes ou membres, ajoutez une annotation pour chaque cible.
Construire des objets avec la réflexion
Utilisez @UsesReflectionToConstruct lorsque votre code instancie une classe par réflexion. Dans l'exemple suivant, un exécuteur de tâches instancie les implémentations d'une interface StartupTask par nom. Comme l'annotation spécifie l'interface, les règles générées conservent le constructeur sans argument de chaque implémentation de StartupTask, tant que R8 conserve 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()
}
}
Appeler des méthodes avec la réflexion
Utilisez @UsesReflectionToAccessMethod lorsque votre code appelle une méthode par réflexion. Remplacez methodName par le nom de la méthode. Dans l'exemple suivant, le code appelle une méthode statique dans une dépendance facultative. La classe n'est pas disponible au moment de la compilation. L'annotation utilise donc className au lieu de 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)
}
}
Accéder aux champs avec la réflexion
Utilisez @UsesReflectionToAccessField lorsque votre code lit ou écrit un champ par réflexion. Définissez fieldName sur le nom du champ :
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)
}
}
Conserver le code appelé depuis l'extérieur de votre application
Utilisez @UnconditionallyKeep pour le code appelé depuis l'extérieur du code Kotlin ou Java de votre application, par exemple une méthode appelée par le code natif via JNI. Annotez l'élément appelé, et non le code qui l'appelle :
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()
}
La règle générée conserve la méthode annotée et la classe qui la contient, avec leurs noms d'origine. Il ne conserve pas les autres classes référencées par la signature de la méthode. Si le code natif dépend de ces classes, annotez-les également ou ajoutez des règles de conservation pour celles-ci. Pour en savoir plus sur JNI et R8, consultez Interaction avec le code natif via JNI.
Par défaut, @UnconditionallyKeep conserve le nom de l'élément. Si l'appelant externe ne dépend pas du nom, définissez shouldPreserveName = false afin que R8 puisse renommer l'élément.
Vérifier les règles générées
Cette section explique comment vérifier les règles générées pour chaque type de module. Pour un module d'application, le plug-in transmet les règles directement à R8. Pour un module de bibliothèque, le plug-in empaquette les règles en tant que règles de conservation des consommateurs dans la sortie de la bibliothèque. Les applications qui dépendent de votre bibliothèque appliquent automatiquement ces règles lorsqu'elles exécutent R8, même si elles n'utilisent pas Jetpack Keep. Pour en savoir plus sur les règles de conservation des consommateurs, consultez Optimisation pour les auteurs de bibliothèques.
Applications Android
Pour vérifier les règles de conservation générées par le plug-in pour un module d'application, exécutez la tâche d'extraction pour une variante de compilation :
./gradlew :app:releaseExtractKeepRules
Le plug-in écrit les règles pour chaque variante dans app/build/generated/<variant>/androidx.annotation.keep.rules.pro. Un commentaire # context: avant chaque règle identifie le code annoté qui l'a produite.
Pour vérifier l'impact des règles générées sur l'optimisation, ainsi que sur vos autres règles de conservation, utilisez l'analyseur de configuration R8.
Bibliothèques Android
Les modules de bibliothèque Android ne nécessitent aucune configuration supplémentaire. Lorsque vous compilez l'AAR, le plug-in ajoute les règles générées au fichier proguard.txt de l'AAR, après les règles de conservation du consommateur que l'AAR contient déjà. Pour ce faire, le plug-in enregistre une tâche <variant>KeepRulesTransformAar pour chaque variante de compilation.
Pour vérifier les règles, compilez l'AAR, par exemple avec ./gradlew
:mylibrary:assembleRelease, puis ouvrez proguard.txt dans l'AAR généré. Les règles générées s'affichent dans une section dont l'en-tête est le suivant :
#################################################################
# Rules generated from androidx.annotation.keep annotations #
#################################################################
Sous macOS et Linux, vous pouvez également afficher les règles à partir de la ligne de commande :
unzip -p mylibrary/build/intermediates/aar/release/releaseKeepRulesTransformAar/mylibrary-release.aar proguard.txt
Bibliothèques Kotlin Multiplatform
Les modules de bibliothèque Kotlin Multiplatform avec une cible Android ne nécessitent pas non plus de configuration supplémentaire. Lorsque vous compilez l'AAR pour la cible Android, le plug-in ajoute les règles générées au fichier proguard.txt de l'AAR, de la même manière que pour les bibliothèques Android. Le plug-in enregistre une tâche androidMainKeepRulesTransformAar pour ce faire.
Pour vérifier les règles, exécutez cette tâche et ouvrez proguard.txt dans l'AAR qu'elle crée. Sous macOS et Linux, vous pouvez imprimer les règles à partir de la ligne de commande :
./gradlew :mylibrary:androidMainKeepRulesTransformAar
unzip -p mylibrary/build/intermediates/aar/androidMain/androidMainKeepRulesTransformAar/mylibrary.aar proguard.txt
Bibliothèques JVM
Avant de pouvoir vérifier les règles d'un module de bibliothèque JVM, configurez le module de sorte que le plug-in ajoute les règles générées à une copie de son fichier JAR. Ensuite, exécutez la tâche de transformation que vous avez enregistrée et vérifiez les fichiers de règles dans le fichier JAR qu'elle crée. Par exemple, si vous avez nommé la tâche keepRulesJar, vous pouvez afficher les règles sur macOS et Linux à l'aide des commandes suivantes :
./gradlew :mylibrary:keepRulesJar
unzip -p mylibrary/build/intermediates/annotation-keep/keepRulesJar/mylibrary-keep-rules.jar \
"META-INF/com.android.tools/*" "META-INF/proguard/*"
Configurer les bibliothèques JVM
Pour les modules de bibliothèque JVM qui produisent un JAR, le plug-in ne modifie pas automatiquement le JAR. Utilisez plutôt la fonction annotationKeep.registerJavaArchiveTransform pour enregistrer une tâche qui crée une copie du fichier JAR avec les règles générées, puis publiez ce fichier JAR. Ajoutez le code suivant au fichier build.gradle.kts du module de bibliothèque :
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)
}
La transformation ajoute les règles générées à l'emplacement des règles R8 ciblées du JAR, META-INF/com.android.tools/r8/proguard.txt. Si le fichier JAR contient déjà des règles dans META-INF/com.android.tools/ ou META-INF/proguard/, la transformation ajoute les règles générées au fichier de règles existant. Pour en savoir plus sur ces emplacements, consultez Prendre en charge différentes versions de R8.
Après avoir enregistré la transformation, vérifiez les règles dans le fichier JAR.
Exemple : Charger une bibliothèque de modules complémentaires facultative
Les bibliothèques utilisent souvent la réflexion pour charger des modules complémentaires facultatifs. Par exemple, un SDK d'analyse peut fournir une bibliothèque principale et un module complémentaire vidéo. Le module complémentaire dépend de la bibliothèque principale. Par conséquent, la bibliothèque principale ne peut pas faire référence directement aux classes du module complémentaire. À la place, la bibliothèque principale recherche la classe du module complémentaire par son nom au moment de l'exécution et n'active l'analyse vidéo que si elle trouve la classe.
Étant donné que la bibliothèque principale n'utilise la classe du module complémentaire que par réflexion, R8 peut supprimer la classe ou son constructeur d'un build de l'application, ce qui entraîne l'échec de la recherche. L'échec ne se produit que dans les builds de l'application qui incluent le module complémentaire. Il est donc difficile de le détecter lors des tests.
Sans Jetpack Keep, le module complémentaire fournit une règle de conservation client pour sa classe, comme indiqué dans Réflexion pour prendre en charge les dépendances facultatives :
# In the add-on's consumer keep rules file.
-keep class com.example.analytics.video.VideoEventTracker {
<init>();
}
Cette règle se trouve dans une bibliothèque différente de celle de la recherche qui en a besoin. Les deux peuvent donc se désynchroniser. Avec Jetpack Keep, vous annotez la recherche dans la bibliothèque principale :
// 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()
}
}
L'annotation suit les consignes suivantes :
- Annotez le code qui effectue la réflexion. L'annotation se trouve sur
initialize, qui appelleClass.forName, et non sur la classe du module complémentaire. - Choisissez l'annotation en fonction de ce que fait le code. La recherche crée une instance de la classe. Le code utilise donc
@UsesReflectionToConstruct. - Utilisez
classNamepour une classe que vous ne pouvez pas référencer. La classe du module complémentaire ne se trouve pas dans le chemin de compilation de la bibliothèque principale. L'annotation ne peut donc pas utiliserclassConstant. - Ne conservez que le constructeur appelé par le code. La recherche appelle le constructeur sans argument.
parameterTypesest donc un tableau vide.
Lorsque vous compilez l'AAR de la bibliothèque principale, le plug-in ajoute une règle qui conserve VideoEventTracker et son constructeur sans argument chaque fois que R8 conserve initialize. Les applications qui utilisent le SDK appliquent cette règle automatiquement, même si elles n'utilisent pas Jetpack Keep. Le module complémentaire n'a donc pas besoin de sa propre règle.
Par rapport à la règle du module complémentaire, cette approche présente les avantages suivants :
- La règle reste à côté de la recherche. La bibliothèque principale possède à la fois la recherche et la règle. Vous les mettez donc à jour ensemble au lieu de coordonner une modification entre deux bibliothèques.
- R8 peut toujours supprimer le code inutilisé. Si une application inclut le module complémentaire, mais n'appelle jamais
initialize, la règle générée ne s'applique pas et R8 peut supprimerVideoEventTracker. La règle du module complémentaire le conserve dans toutes les applications qui l'incluent. - Lint détecte les recherches non annotées. Si la bibliothèque principale ajoute ultérieurement une autre recherche par réflexion sans annotation, lint la signale afin que vous puissiez la corriger avant de publier la bibliothèque.
Pour confirmer le résultat, vérifiez que la règle générée apparaît dans le fichier proguard.txt de l'AAR, comme décrit dans Bibliothèques Android. Ensuite, testez une version de l'application qui inclut le module complémentaire, car c'est là qu'une règle manquante pourrait interrompre l'analyse vidéo.
Limites
Jetpack Keep ne détecte pas encore la réflexion qui trouve des classes et des membres par leurs annotations, ni ne génère de règles de conservation pour celle-ci. Cette fonctionnalité est prévue.
En attendant, utilisez des règles de conservation pour ce type de réflexion. Par exemple, la règle suivante conserve tous les membres annotés avec @SomeAnnotation dans les classes conservées par R8 :
-keepclassmembers class * {
@com.example.annotation.SomeAnnotation <methods>;
@com.example.annotation.SomeAnnotation <fields>;
}
Si Jetpack Keep ne fonctionne pas pour votre cas d'utilisation ou si vous avez d'autres commentaires, signalez un problème.
Ressources supplémentaires
- Exemple Jetpack Keep : projet Android Studio qui utilise Jetpack Keep dans une application, une bibliothèque Android, une bibliothèque Kotlin Multiplatform et une bibliothèque JVM.
- Cas d'utilisation et exemples de règles de conservation : règles de conservation pour les scénarios de réflexion et JNI courants.
- Suivez les bonnes pratiques : conseils pour rédiger des règles de conservation à portée limitée.