En tant qu'auteur de bibliothèque, vous devez vous assurer que les développeurs d'applications peuvent facilement intégrer votre bibliothèque dans leur application tout en maintenant une expérience utilisateur finale de haute qualité. Cela signifie que votre bibliothèque doit être compatible avec l'optimisation Android (R8) sans nécessiter de configuration supplémentaire de la part du développeur, ou indiquer que la bibliothèque peut ne pas être adaptée à une utilisation sur Android. Il est essentiel que les bibliothèques destinées à être utilisées sur Android n'empêchent pas les optimisations importantes des applications et respectent les exigences d'optimisation supplémentaires.
Cette documentation s'adresse aux développeurs de bibliothèques publiées, mais peut également être utile aux développeurs de modules de bibliothèque internes dans une grande application modularisée.
Si vous êtes développeur d'applications et que vous souhaitez en savoir plus sur l'optimisation de votre application Android, consultez Activer l'optimisation des applications. Pour savoir quelles bibliothèques utiliser, consultez Choisir judicieusement les bibliothèques.
Comprendre les types de règles de conservation
Il existe deux types distincts de règles de conservation que vous pouvez appliquer dans les bibliothèques :
- Les règles de conservation des consommateurs doivent spécifier les règles qui conservent ce que la bibliothèque reflète. Si une bibliothèque utilise la réflexion ou JNI pour appeler son code ou le code défini par une application cliente, ces règles doivent décrire le code à conserver. Les bibliothèques doivent regrouper les règles de conservation des consommateurs, qui utilisent le même format que les règles de conservation des applications. Ces règles sont regroupées dans des artefacts de bibliothèque (AAR ou JAR) et sont utilisées automatiquement lors de l'optimisation de l'application Android lorsque la bibliothèque est utilisée. Ces règles sont conservées dans le fichier spécifié avec la propriété
consumerProguardFilesdans votre fichierbuild.gradle.kts(oubuild.gradle). Pour en savoir plus, consultez Écrire des règles de conservation pour les consommateurs. - Les règles de conservation de la compilation de la bibliothèque sont appliquées lorsque votre bibliothèque est compilée. Elles ne sont nécessaires que si vous décidez d'optimiser partiellement votre bibliothèque au moment de la compilation. Ils doivent veiller à ce que l'API publique de la bibliothèque ne soit pas supprimée, sinon l'API publique ne sera pas présente dans la distribution de la bibliothèque, ce qui signifie que les développeurs d'applications ne pourront pas utiliser la bibliothèque. Ces règles sont conservées dans le fichier spécifié avec la propriété
proguardFilesdans votre fichierbuild.gradle.kts(oubuild.gradle). Pour en savoir plus, consultez Optimiser la compilation de la bibliothèque AAR.
Exigences et consignes d'optimisation
La configuration R8 dans les bibliothèques a un impact global sur la taille et les performances binaires finales de l'application consommatrice. En plus des bonnes pratiques générales concernant les règles de conservation, les auteurs de bibliothèques doivent respecter des exigences spécifiques et tenir compte de consignes supplémentaires.
Respecter les exigences d'optimisation
L'inefficacité des bibliothèques contribue fortement à l'encombrement des applications, au gaspillage de mémoire, aux démarrages lents et aux erreurs ANR (Application Not Responding). Les bibliothèques doivent éviter de ne pas respecter les exigences suivantes afin de ne pas réduire considérablement la qualité de l'application et l'expérience utilisateur.
Pas de règles de conservation générales ni au niveau du package : votre bibliothèque ne doit pas inclure de règles de conservation générales qui conservent la majeure partie du code de votre bibliothèque ou d'une autre bibliothèque. Les règles de conservation générales peuvent résoudre les plantages à court terme, mais elles augmentent la taille de l'application de toutes les applications qui consomment votre bibliothèque.
N'incluez pas de règles de conservation à l'échelle du package (telles que
-keep class com.mylibrary.** {*; }) pour les packages de votre bibliothèque ou d'autres bibliothèques référencées. Ces règles limitent l'optimisation de ces packages dans toutes les applications qui consomment votre bibliothèque.Pas de règles globales inappropriées : n'utilisez jamais d'options globales telles que
-dontobfuscateou-allowaccessmodification.Utilisez la génération de code plutôt que la réflexion chaque fois que possible : dans la mesure du possible, utilisez la génération de code (codegen) plutôt que la réflexion. La génération de code et la réflexion sont deux approches courantes pour éviter le code récurrent lors de la programmation, mais la génération de code est plus compatible avec un optimiseur d'application comme R8.
Avec la génération de code, le code est analysé et modifié pendant le processus de compilation. Comme il n'y a pas de modifications majeures après la compilation, l'optimiseur sait quel code est finalement nécessaire et ce qui peut être supprimé sans risque.
Avec la réflexion, le code est analysé et manipulé au moment de l'exécution. Étant donné que le code n'est pas réellement finalisé tant qu'il n'est pas exécuté, l'optimiseur ne sait pas quel code peut être supprimé en toute sécurité. Il supprimera probablement le code utilisé de manière dynamique par réflexion lors de l'exécution, ce qui entraînera des plantages de l'application pour les utilisateurs.
De nombreuses bibliothèques modernes utilisent la génération de code au lieu de la réflexion. Consultez KSP pour obtenir un point d'entrée commun, utilisé par Room, Dagger2 et bien d'autres.
Compatibilité avec le mode complet R8 : votre bibliothèque ne doit pas planter lorsque le mode complet R8 est activé. Le mode complet de R8 est le mode recommandé pour utiliser R8. Il est défini par défaut depuis AGP 8.0, qui a été stabilisé en 2023. Si votre bibliothèque plante sous R8, la solution consiste à identifier le point d'entrée de réflexion ou JNI spécifique et à ajouter une règle ciblée, et non à conserver l'intégralité du package.
Autres recommandations
En plus des exigences d'optimisation, voici quelques recommandations supplémentaires.
- N'utilisez pas
-repackageclassesdans le fichier de règles de conservation du consommateur de votre bibliothèque. Toutefois, pour optimiser la compilation de votre bibliothèque, vous pouvez utiliser-repackageclassesavec un nom de package interne, tel que<your.library.package>.internal, dans le fichier de règles de conservation de la compilation de votre bibliothèque. Cela peut améliorer l'efficacité de votre bibliothèque dans les applications non optimisées. Toutefois, ce n'est généralement pas nécessaire, car les applications doivent également être optimisées. - Déclarez tous les attributs dont votre bibliothèque a besoin pour fonctionner dans les fichiers de règles de conservation de votre bibliothèque, même s'il peut y avoir un chevauchement avec les attributs définis dans
proguard-android-optimize.txt. - Si vous avez besoin des attributs suivants dans la distribution de votre bibliothèque, conservez-les dans le fichier de règles de conservation de compilation de votre bibliothèque, et non dans le fichier de règles de conservation du consommateur de votre bibliothèque :
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- Les auteurs de bibliothèques doivent conserver l'attribut
RuntimeVisibleAnnotationsdans leurs règles de conservation des consommateurs si des annotations sont utilisées lors de l'exécution. - Les auteurs de bibliothèques ne doivent pas utiliser les options globales suivantes dans leurs règles de conservation des consommateurs :
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
Quand la réflexion est acceptable
Si vous devez utiliser la réflexion, vous ne devez le faire que dans l'un des éléments suivants :
- Types ciblés spécifiques (implémenteurs d'interface ou sous-classes spécifiques)
- Coder à l'aide d'une annotation d'exécution spécifique
L'utilisation de la réflexion de cette manière limite le coût d'exécution et permet d'écrire des règles de conservation ciblées pour les consommateurs.
Cette forme de réflexion spécifique et ciblée est un modèle que vous pouvez voir à la fois dans le framework Android (par exemple, lors de l'inflation des activités, des vues et des drawables) et dans les bibliothèques AndroidX (par exemple, lors de la construction de WorkManager
ListenableWorkers ou RoomDatabases). En revanche, la réflexion ouverte de Gson ne convient pas à l'utilisation dans les applications Android.
Idées reçues
Certaines idées fausses courantes peuvent vous amener à configurer R8 de manière incorrecte. En voici quelques exemples :
Compréhension incorrecte des optimisations de R8 : contrairement à ce que l'on pense souvent, les optimisations de R8 ne se limitent pas à l'obscurcissement, mais incluent également la réduction du code et les optimisations logiques avec les techniques d'intégration de méthodes et de fusion de classes. Pour en savoir plus, consultez Présentation de l'optimisation R8.
Ignorer l'optimisation des bibliothèques obscurcies : une erreur courante consiste à omettre une bibliothèque de l'optimisation, car elle a été optimisée ou obscurcie lors de sa compilation dans un fichier AAR (Android Archive) ou JAR (Java Archive). Les optimisations lors de la compilation de la bibliothèque sont limitées. Votre application ne doit pas désactiver l'optimisation de la bibliothèque en l'incluant dans une règle de conservation. Pour en savoir plus, consultez Optimiser la compilation de la bibliothèque AAR.
Compréhension incorrecte de l'option
-keep: la règle-keepempêche R8 d'exécuter l'un de ses passes d'optimisation. Pour en savoir plus, consultez Choisir la bonne option de conservation.
Configurer le packaging des règles
Pour vous assurer que vos règles de conservation des consommateurs sont correctement appliquées, vous devez les regrouper de manière appropriée en fonction du format de votre bibliothèque.
Bibliothèques AAR
Pour ajouter des règles de consommateur à une bibliothèque AAR, utilisez l'option consumerProguardFiles dans le script de compilation du module de bibliothèque Android. Pour en savoir plus, consultez nos conseils sur la création de modules de bibliothèque.
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
Groovy
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
Bibliothèques JAR
Pour regrouper des règles avec votre bibliothèque Kotlin ou Java fournie sous forme de fichier JAR, placez votre fichier de règles dans le répertoire META-INF/proguard/ du fichier JAR final, avec n'importe quel nom de fichier.
Par exemple, si votre code se trouve dans <libraryroot>/src/main/kotlin, placez un fichier de règles du consommateur dans <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro. Les règles seront alors regroupées au bon endroit dans votre fichier JAR de sortie.
Vérifiez que les règles sont correctement regroupées dans les fichiers JAR finaux en vous assurant qu'elles se trouvent dans le répertoire META-INF/proguard.
Optimiser la compilation de la bibliothèque AAR (avancé)
En général, vous n'avez pas besoin d'optimiser directement la compilation d'une bibliothèque, car les optimisations possibles lors de la compilation de la bibliothèque sont très limitées. En tant que développeur de bibliothèque, vous devez réfléchir à plusieurs étapes d'optimisation et conserver le comportement, à la fois au moment de la compilation de la bibliothèque et de l'application, avant d'optimiser cette bibliothèque.
Si vous souhaitez toujours optimiser votre bibliothèque au moment de la compilation, le plug-in Android Gradle est compatible.
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
Groovy
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
Notez que le comportement de proguardFiles est très différent de celui de consumerProguardFiles :
- Les
proguardFilessont utilisés au moment de la compilation, souvent avecgetDefaultProguardFile("proguard-android-optimize.txt"), pour définir la partie de votre bibliothèque qui doit être conservée lors de la compilation de la bibliothèque. Il s'agit au minimum de votre API publique. - Les
consumerProguardFiles, en revanche, sont empaquetés dans la bibliothèque pour affecter les optimisations qui se produiront plus tard, lors de la compilation d'une application qui consomme votre bibliothèque.
Par exemple, si votre bibliothèque utilise la réflexion pour construire des classes internes, vous devrez peut-être définir les règles de conservation à la fois dans proguardFiles et consumerProguardFiles.
Si vous utilisez -repackageclasses dans la compilation de votre bibliothèque, reconditionnez les classes dans un sous-package à l'intérieur du package de votre bibliothèque. Par exemple, utilisez -repackageclasses
'com.example.mylibrary.internal' au lieu de -repackageclasses 'internal'.
Assurer la compatibilité avec différentes versions de R8 (avancé)
Vous pouvez personnaliser les règles pour cibler des versions spécifiques de R8. Cela permet à votre bibliothèque de fonctionner de manière optimale dans les projets qui utilisent des versions R8 plus récentes, tout en permettant aux règles existantes de continuer à être utilisées dans les projets avec des versions R8 plus anciennes.
Pour spécifier des règles R8 ciblées, vous devez les inclure dans le répertoire META-INF/com.android.tools à l'intérieur de classes.jar d'un fichier AAR ou dans le répertoire META-INF/com.android.tools d'un fichier JAR.
In an AAR library:
proguard.txt (legacy location, the file name must be "proguard.txt")
classes.jar
└── META-INF
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
In a JAR library:
META-INF
├── proguard/<ProGuard-rule-files> (legacy location)
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
Dans le répertoire META-INF/com.android.tools, il peut y avoir plusieurs sous-répertoires dont le nom est au format r8-from-<X>-upto-<Y> pour indiquer les versions R8 pour lesquelles les règles sont écrites. Chaque sous-répertoire peut contenir un ou plusieurs fichiers contenant les règles R8, avec n'importe quel nom de fichier et extension.
Notez que les parties -from-<X> et -upto-<Y> sont facultatives, que la version <Y> est exclusive et que les plages de versions sont généralement continues, mais peuvent également se chevaucher.
Par exemple, r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 et r8-from-8.2.0 sont des noms de répertoire représentant un ensemble de règles R8 ciblées. Les règles du répertoire r8 peuvent être utilisées par toutes les versions de R8. Les règles du répertoire r8-from-8.0.0-upto-8.2.0 peuvent être utilisées par R8 à partir de la version 8.0.0 jusqu'à la version 8.2.0 (non incluse).
Le plug-in Android Gradle utilise ces informations pour sélectionner toutes les règles qui peuvent être utilisées par la version R8 actuelle. Si une bibliothèque ne spécifie pas de règles R8 ciblées, le plug-in Android Gradle sélectionne les règles à partir des anciens emplacements (proguard.txt pour un fichier AAR ou META-INF/proguard/<ProGuard-rule-files> pour un fichier JAR).