Le système de compilation de la plate-forme Android (Soong) exécute le compilateur R8 sur les cibles qui compilent le bytecode DEX, y compris les applications Android (android_app), les tests (android_test) et les bibliothèques Java installables avec compile_dex: true (comme services.jar), pour réduire, optimiser et supprimer le code et les ressources inutilisés. Sur les bibliothèques statiques (android_library et java_library statique), Soong n'exécute pas R8 directement, mais utilise le bloc de propriété optimize pour associer et propager les règles de conservation des consommateurs (export_proguard_flags_files: true) aux cibles DEX en aval qui les associent de manière statique.
Pour les ingénieurs de plate-forme qui créent des images système ou des packages de fournisseurs, l'élimination des règles de conservation trop larges offre des avantages directs pour l'état du système :
- Empreinte de partition système plus petite : R8 supprime les classes, méthodes et ressources inutilisées avant d'empaqueter les APK et les JAR sur
/system,/system_ext,/productet/vendor. - Artefacts compilés plus petits : moins de méthodes DEX signifie que les fichiers
.odexet.vdexgénérés pardex2oatlors de la compilation ou de la compilation sur l'appareil sont plus petits. - Réduction de la pression sur la mémoire d'exécution : étant donné qu'Android mappe les tables de ressources
.odex,.vdexet APK dans la mémoire du processus à l'aide d'appelsmmappaginés à la demande, des binaires plus petits réduisent les erreurs de page majeures lors du démarrage de l'application et diminuent la mémoire du code et des ressources résidents entre les processus. Pour en savoir plus sur l'impact du code d'application compilé sur la mémoire de l'appareil, consultez Le code d'application est de la mémoire. - Optimisation plus efficace de l'ensemble du programme : les contraintes de conservation étroites permettent à R8 d'intégrer des méthodes, de dévirtualiser les appels d'interface et virtuels, de supprimer les champs inutilisés et de propager des constantes entre les classes.
Configurer l'optimisation R8 dans Soong
Dans les fichiers Android.bp, configurez l'optimisation R8 à l'aide du bloc de propriété optimize sur les cibles android_app et java_library installables (ou sur les modules android_library et java_library statiques pour exporter les règles de conservation des consommateurs) :
android_app {
name: "MySystemApp",
srcs: ["src/**/*.java"],
optimize: {
obfuscate: true,
shrink_resources: true,
},
}
Le tableau suivant récapitule les propriétés optimize les plus courantes dans Soong (définies dans build/soong/java/dex.go) :
| Propriété | Description |
|---|---|
enabled |
Détermine si R8 s'exécute sur la cible. La valeur par défaut est true pour toutes les cibles android_app.
|
shrink |
Contrôle le tree shaking pour supprimer les classes, champs et méthodes inaccessibles. La valeur par défaut est true pour les cibles android_app (false pour les modules java_library et de test autonomes, qui transmettent -dontshrink, sauf s'ils sont explicitement définis).
|
optimize |
Contrôle les optimisations du bytecode, telles que l'intégration de méthodes, la fusion de classes, la propagation de constantes et la suppression des branches mortes. La valeur par défaut est true pour les cibles android_app (contrôlée par l'option de compilation de version RELEASE_R8_OPTIMIZE_BY_DEFAULT).
|
obfuscate |
Contrôle la minimisation et le changement de nom des identifiants. La valeur par défaut est false pour des raisons de compatibilité historique, mais définissez-la sur true dans la mesure du possible pour réduire la taille du fichier DEX et débloquer des optimisations plus poussées (voir Activer l'obscurcissement dans la mesure du possible).
|
shrink_resources |
Supprime les ressources inutilisées (entrées res/) de l'APK empaqueté après la minification de code. La valeur par défaut est false.
|
optimized_shrink_resources |
Exécute le pipeline intégré de minification du code et des ressources R8 afin que R8 trace conjointement les références de code et de ressources en une seule passe. Lorsque shrink_resources: true est défini, la valeur par défaut est RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT (true dans les builds de plate-forme standards).
|
proguard_flags_files |
Répertorie les fichiers .flags ou .pro spécifiques au module contenant des règles de conservation personnalisées. Évitez d'ajouter des fichiers personnalisés lorsque les annotations standards ou les valeurs par défaut suffisent.
|
export_proguard_flags_files |
Propager le proguard_flags_files de cette bibliothèque aux modules en aval qui en dépendent de manière statique.
|
trace_references_from |
Répertorie les cibles dépendantes de la compilation DEX directe (connectées via instrumentation_for ou libs) dont les références de bytecode dans cette cible sont automatiquement tracées et conservées par R8 (également propagées dans les variantes override_android_app et override_android_test, et compatibles avec les bibliothèques Java associées non dépendantes lorsqu'elles sont associées à une division .impl).
|
allow_traced_references |
Permet aux cibles dépendantes de la compilation DEX directe (connectées via instrumentation_for ou libs, telles que les tests multi-dépôts ou de fournisseurs qui ne peuvent pas être listés dans trace_references_from) d'activer le traçage de leurs références de bytecode dans cette cible en définissant trace_references: true.
|
trace_references |
Lorsque cette option est définie sur true sur un dépendant de compilation DEX directe d'une cible avec allow_traced_references: true, exécute R8 tracereferences sur ce module et transmet les règles de conservation générées au module cible.
|
protect_api_surface |
Transmet --protect-api-surface à R8. Lorsqu'il est défini sur un java_sdk_library, il génère et associe automatiquement des règles de conservation de l'API Metalava dans tous les champs d'application de l'API activés (public, système, module-lib, serveur système et test) à la bibliothèque d'implémentation.
|
Les bibliothèques statiques (android_library, java_library et importations AAR) peuvent également définir required_downstream_optimization: { enabled: true, optimize: true } dans Android.bp pour exiger que toute cible de compilation DEX en aval qui les associe de manière statique (directement ou de manière transitive) active la minification R8 et l'optimisation du bytecode. Soong l'active automatiquement sur androidx.compose.runtime_runtime afin que les cibles associées à Jetpack Compose soient optimisées avec R8.
Activer l'obscurcissement chaque fois que possible
Dans Soong, obfuscate est défini par défaut sur false pour des raisons de compatibilité historique, mais vous devez définir explicitement obfuscate: true sur android_app et les cibles DEX autonomes dans la mesure du possible :
- Empreinte DEX et
.vdexréduite : renommer les packages, les classes, les champs et les méthodes en identifiants courts (a,b) réduit le pool de chaînes DEX (string_idsetstring_data_item) et les descripteurs de type. Comme Android mappe les fichiers.vdexet.odexdans la mémoire du processus, des tables de symboles plus petites réduisent directement la taille de la partition système et l'espace mémoire utilisé lors de l'exécution. - Optimisations plus poussées de l'ensemble du programme : autoriser R8 à renommer les identifiants permet de fusionner les classes, d'aplatir les packages et de dédupliquer les classes synthétiques dans les packages que R8 doit sinon ignorer pour éviter les conflits de noms.
- Symbolisation complète de la trace de la pile : l'activation de l'obscurcissement dans Soong ne réduit pas la capacité de débogage. Pour chaque cible compilée par R8, Soong émet un fichier de mappage
proguard_dictionarydans le répertoire intermédiaire du module, regroupe tous les dictionnaires de module dans l'artefactproguard-dict.zipde la compilation, intègre un hachage de mappage (--map-id-template) dans l'en-tête DEX et réécrit les attributsSourceFilede la classe (--source-file-template) afin queretracepuisse symboliser automatiquement les traces de la pile.
Conservez obfuscate: false (ou préservez explicitement les noms d'API publics) uniquement pour :
- Bibliothèques partagées sur le bootclasspath ou le classpath
system_server: les modules (java_libraryoujava_sdk_library) qui exposent une surface d'API externe associée de manière dynamique par d'autres modules au moment de l'exécution (comme les ciblesframework.jar,services.jarou<uses-library>) doivent conserverobfuscate: falseou préserver explicitement leur surface d'API à l'aide des règlesprotect_api_surface: true,@KeepForApiou-keepafin que les appelants compilés par rapport à leurs bouchons puissent résoudre les noms de classe et de membre au moment de l'exécution. Sur les modulesjava_sdk_library, la définition deprotect_api_surface: truegénère et associe automatiquement les règles de conservation ProGuard de Metalava à tous les champs d'application d'API activés (public, système, module-lib, system-server et test) à la bibliothèque d'implémentation. Lorsque l'optimisation R8 généralisée est activée sur des cibles bootclasspath non modifiables (framework-minus-apex,extetframework-location), elles incluent égalementframework-hiddenapi-proguard-rules(généré parhiddenapi_to_proguard) pour conserver les membres listés dans les fichiersboot/hiddenapi/*.txtactifs (en omettanthiddenapi-max-target-o.txt).
Avant d'activer obfuscate: true sur une application, vérifiez les conditions préalables de migration suivantes :
- Dépendances de l'APK de test externe : si un APK
android_testexterne définitinstrumentation_for: "MyApp"et appelle directement des classes ou des méthodes internes deMyApp, le changement de nom de ces symboles entraîneNoSuchMethodErrorouNoClassDefFoundErrorau moment de l'exécution du test. Configureztrace_references_from(ouallow_traced_referencesavectrace_references: true), structurez le test en tant qu'APK d'auto-instrumentation associantMyApp.implde manière statique, ou annotez les hooks de test avec@VisibleForTesting(voir Étape 2 : Migrer les règles de conservation basées sur les tests) afin que les symboles internes nécessaires aux tests soient conservés tout en obscurcissant le reste de l'application. - Réflexion basée sur des chaînes et JNI : si un module recherche des classes, des méthodes ou des champs par des noms de chaînes littérales (
Class.forName,getDeclaredMethodou JNIFindClassetGetMethodID) sans règles de conservation ni annotations, l'obscurcissement renomme ces cibles et interrompt les recherches d'exécution. (Notez que la réflexion sans annotation échoue également sousshrink: trueetoptimize: true.) Annotez ces points d'entrée avec le Guide des annotations Keep (@UsesReflection,@UsedByReflectionou@UsedByNative) afin que R8 conserve leurs noms et obscurcisse le reste du module.
Suivre le principe de l'absence d'indicateurs personnalisés
Le module de plate-forme Android idéal ne comporte pas de fichiers proguard.flags ni keep.xml personnalisés. Dans la compilation de la plate-forme Android, la grande majorité des règles de conservation personnalisées sont redondantes :
- Lignes de base de la plate-forme et AAPT2 : la plupart des points d'entrée sont déjà conservés automatiquement par les lignes de base de la plate-forme globale (comme
@Keep, les méthodes JNInativeet@VisibleForTesting) ou générés par AAPT2 à partir deAndroidManifest.xmlet des ressources de mise en page (voir Comprendre les règles de conservation par défaut de la plate-forme). - Alternatives ciblées : lorsque les points d'entrée doivent être conservés, privilégiez les annotations de site de déclaration (
keepanno,@VisibleForTesting), les règles de bibliothèque exportées (export_proguard_flags_files: true) ou l'association de tests statiques par rapport aux fichiers.flagsdissociés (voir Auditer et migrer les règles de conservation existantes).
Avant d'ajouter ou de conserver un fichier proguard.flags ou keep.xml personnalisé, vérifiez si la règle est déjà gérée par les références par défaut ou si elle peut être migrée vers des annotations de code.
Comprendre les règles de conservation par défaut de la plate-forme
Soong transmet automatiquement les règles de référence globales suivantes à chaque appel de R8 sur les cibles de compilation DEX (modules android_app, android_test et java_library installables), configurées dans build/soong/java/dex.go :
build/make/core/proguard.flags: conserve les classes et les membres annotés avec@com.android.internal.annotations.VisibleForTestingdans tous les packages, et avec@VisibleForTesting(androidx.annotation.VisibleForTestingoucom.google.common.annotations.VisibleForTesting) dans les packagesandroid.**,com.android.**etcom.google.android.**. Conserve également@TestApi,@Keep(androidx.annotation,android.support.annotation,com.android.internal.annotations),@KeepForWeakReference,@WeaklyReferencedCallbacket@dalvik.annotation.optimization.**.build/make/core/proguard_basic_keeps.flags: conserve les attributsSourceFilepour les traces de la pile, les annotations de visibilité de l'environnement d'exécution (RuntimeVisible*Annotations), les attributsExceptionsetAnnotationDefault, les méthodesnative, les membresSerializable, les méthodes@JavascriptInterface, les constructeursThrowable(String), les champsParcelable$CREATORet les champs protobufMessageLite.build/make/core/proguard/kotlin.flags: désactive les avertissements inoffensifs pour des méta-annotations Kotlin spécifiques (kotlin.Metadataetkotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) et supprime les annotations KotlinDebugMetadatadans les builds de release.build/make/core/proguard/checknotnull.flags: remplace les appels d'assistance de vérification de valeur nulle courants (com.google.common.base.Preconditions.checkNotNulletdagger.internal.Preconditions.checkNotNull*) par des vérifications de valeur nulle de bytecode concises. Ce fichier omet intentionnellementObjects.requireNonNullpour conserver les messages d'exception explicites au-delà des limites de l'API du framework.build/make/core/proguard/enumvalues.flags: conserve les méthodesvaluesetvalueOfsur les typesenum, sauf si elles sont désactivées par la configuration de compilation.- Règles générées automatiquement par AAPT2 : AAPT2 inspecte les fichiers
AndroidManifest.xml, XML de mise en page et XML de préférence fusionnés pour générer des règles de conservation exactes pour chaqueActivity,Service,BroadcastReceiver,ContentProvider,BackupAgent,Application, sous-classeViewpersonnalisée, sous-classePreferenceet méthode XMLandroid:onClickenregistrés.
Auditer et migrer les règles de conservation existantes
Lorsque vous auditez des fichiers proguard.flags ou keep.xml existants dans un dépôt de plate-forme, évaluez chaque règle dans l'ordre par rapport à la hiérarchie en quatre étapes suivante :
Étape 1 : Supprimez les règles redondantes ou obsolètes
Supprimez les règles qui sont déjà couvertes par les références globales, les règles de mise en page et de fichier manifeste AAPT2 ou les valeurs par défaut de R8, ainsi que les règles faisant référence à des classes ou des packages qui n'existent plus.
Si toutes les règles d'un fichier .flags sont redondantes, supprimez le fichier et supprimez proguard_flags_files de Android.bp. Si le bloc optimize restant ne fait que répéter les paramètres par défaut, supprimez le bloc redondant et formatez le fichier de compilation avec bpfmt -w Android.bp. Lorsque vous nettoyez un package, vérifiez si les modules associés dans les sous-répertoires (comme les variantes de cible Kotlin) font référence au même fichier d'options et mettez-les à jour ensemble.
Étape 2 : Migrer les règles de conservation basées sur des tests
Déplacez les règles de conservation basées sur les tests hors des fichiers .flags de production personnalisés à l'aide de l'un des modèles suivants :
Tracer les références à partir de cibles de test ou d'accompagnement (
trace_references_fromouallow_traced_references) : lorsque la cible et un module dépendant direct compilent leur propre DEX (android_app,android_testou unjava_librarydexé) et sont connectés viainstrumentation_foroulibs, Soong peut exécuter R8tracereferencessur le module dépendant et intégrer les règles de conservation générées dans la compilation R8 de la cible sans créer de cycle de dépendance ni nécessiter une division manuelle de la bibliothèque.impl:Tests dans le même dépôt (
trace_references_from) : listez les dépendancesinstrumentation_foroulibsdirectes sur le module cible (cela se propage également automatiquement entre les variantesoverride_android_appetoverride_android_test, et continue de prendre en charge les bibliothèques Java associées non dépendantes lorsqu'elles sont associées à une division.impl) :android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { obfuscate: true, shrink_resources: true, trace_references_from: ["MyAppTests"], }, } android_test { name: "MyAppTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], }Tests inter-dépôts ou de fournisseurs (
allow_traced_referencesettrace_references) : lorsque le module cible ne peut pas lister directement la dépendance danstrace_references_from(par exemple, les tests dans un autre dépôt ou une autre arborescence de fournisseurs), définissezallow_traced_references: truesur le module cible et activez l'option à partir de chaque dépendance avectrace_references: true:android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { allow_traced_references: true, }, } android_test { name: "MyAppVendorTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], optimize: { trace_references: true, }, }Réflexion basée sur des chaînes dans les tests :
tracereferencesinspecte les références du pool de constantes de bytecode et ne peut pas détecter la réflexion basée sur des chaînes dans les tests (Class.forName,getDeclaredField,getDeclaredMethodou les assistants d'injection par champs réflexive). Annotez les champs et les méthodes de production accessibles de manière réfléchie avec@VisibleForTesting(ou@UsedByReflection).
Associer la bibliothèque d'implémentation aux tests (
static_libs) : si vous ne souhaitez conserver aucun point d'entrée réservé aux tests dans l'APK de production et que vous ne voulez pas que les modifications de test déclenchent à nouveau R8 sur la cible de production, placez les fichiers sources de l'application dans unandroid_library(MyApp.impl) et associez de manière statiqueMyApp.implàMyAppetMyAppTests:android_library { name: "MyApp.impl", srcs: ["src/**/*.java"], manifest: "AndroidManifest.xml", } android_app { name: "MyApp", static_libs: ["MyApp.impl"], optimize: { obfuscate: true, shrink_resources: true, }, } android_test { name: "MyAppTests", srcs: ["tests/**/*.java"], static_libs: ["MyApp.impl"], }Ce modèle à trois modules permet à
MyAppTestsde s'exécuter en tant que test d'instrumentation avec un accès complet aux classes internes, tout en permettant àMyAppde réduire et d'obscurcir uniquement les points d'entrée de production.Annoter les hooks de test avec
@VisibleForTesting: si un APK de test externe n'appelle que quelques méthodes ou constructeurs internes sur une cibleandroid_app, vous pouvez annoter ces déclarations avec@VisibleForTesting. La référence globalebuild/make/core/proguard.flagsconserve automatiquement les éléments@VisibleForTestingdans les packagesandroid.**,com.android.**etcom.google.android.**sans fichiers.flagspersonnalisés. (Pour les modules de fournisseurs en dehors de ces espaces de noms, utiliseztrace_references_from,@UsedByReflectionou des règles de bibliothèque exportées.) Lorsque vous annotez manuellement avec@VisibleForTestingau lieu d'utilisertrace_references_fromoutrace_references, annotez également les classes transmises à Mockitomock(...)ouspy(...)(pas seulement les méthodes bouchonnées), carmockito-target(dexmaker-mockito) sur l'appareil génère des sous-classes d'exécution et R8 peut marquer les classes sans sous-classefinal(MockitoException: Can't mock/spy class ... - final class).Évitez ou reconditionnez les bibliothèques statiques qui se chevauchent (
jarjar_rules) : lorsqu'un APKandroid_appet son APK de testinstrumentation_forassocient de manière statique la même bibliothèque (directement, commeguava, ou de manière transitive),tracereferencesvoit ces classes définies dans le test et n'émet aucune règle de conservation pour l'application. Au moment de l'exécution, le chargeur de classe parent de l'application masque la copie du test avec ses classes réduites par R8, ce qui entraîne une erreurIllegalAccessErrorouNoSuchMethodError. Si le test n'a pas besoin de sa propre copie groupée, supprimez le doublon dustatic_libsdu test (ou associez-le vialibs) pour éviter la collision. Sinon, reconditionnez les packages de bibliothèque statique qui se chevauchent de l'application sous un préfixe spécifique au module à l'aide dejarjar_rules.
Étape 3 : Exportez les règles de la bibliothèque propriétaire
Si un java_library ou un android_library partagé nécessite des règles de conservation pour sa propre réflexion interne ou ses rappels JNI, définissez les règles sur la cible de la bibliothèque et définissez export_proguard_flags_files: true :
java_library {
name: "my-shared-library",
srcs: ["src/**/*.java"],
optimize: {
proguard_flags_files: ["proguard.flags"],
export_proguard_flags_files: true,
},
}
Toutes les cibles android_app et de bibliothèque en aval qui associent de manière statique my-shared-library héritent automatiquement de ces règles. Les applications en aval n'ont donc pas besoin de les dupliquer. Lorsque plusieurs modules de différents répertoires doivent partager un ensemble de règles qui n'est pas lié à une seule bibliothèque de code, publiez le fichier .flags via un filegroup explicite dans Android.bp afin que les modules puissent référencer :my-shared-flags proprement au-delà des limites du package. Pour obtenir des conseils généraux sur la création de règles de conservation des consommateurs de bibliothèques sans restreindre l'optimisation des applications en aval, consultez Optimisation pour les auteurs de bibliothèques.
Étape 4 : Annoter les déclarations dans le code source
Lorsqu'une classe, une méthode, un constructeur ou un champ est accessible via la réflexion ou JNI et n'est pas couvert par AAPT2 ou les références globales, remplacez les règles -keep dissociées dans les fichiers .flags par des annotations sources du Guide to Keep Annotations (voir la documentation de référence Javadoc keepanno) :
@UsesReflection: privilégiez cette annotation lorsque vous contrôlez le code qui effectue la réflexion. Placez-la au niveau du site d'appel de réflexion pour déclarer les classes, méthodes ou champs cibles auxquels l'accès est dynamique. Étant donné que@UsesReflectionencode automatiquement une condition préalable selon laquelle le site d'appel annoté lui-même est accessible, R8 supprime l'appelant et la cible de réflexion si le site d'appel n'est pas utilisé.@UsedByReflection: à placer sur les classes, méthodes, champs ou constructeurs instanciés ou appelés de manière réfléchie par du code ou des bibliothèques externes, comme les classes chargées avecClass.forNameà partir des clésBundleouSettings, ou les classes de plug-ins chargées via des chargeurs de classe dynamiques. Spécifiezkind(par exemple,KeepItemKind.CLASS_AND_METHODS),preconditions(@KeepCondition) et les contraintes de paramètre afin que R8 ne conserve que le contrat de réflexion exact et puisse toujours optimiser ou supprimer les membres inutilisés de la classe. N'ajoutez pas@UsedByReflectionaux composants enregistrés dans le fichier manifeste, tels queJobServiceouBroadcastReceiver, car AAPT2 les conserve déjà.@UsedByNative: à placer sur les méthodes ou les champs accessibles à partir du code JNI C ou C++ à l'aide deGetMethodID,GetStaticMethodIDouGetFieldID.@KeepForApi: à placer sur les classes ou les membres de l'API de bibliothèque qui doivent rester intacts lorsqu'une bibliothèque est réduite avant d'être distribuée.@Keep(androidx.annotation.Keep) : à utiliser comme solution de secours lorsquekeepannon'est pas applicable. L'application de@Keepà une classe conserve la classe et tous ses membres sans condition. L'application de@Keepà une méthode ou à un champ agit comme un point d'entrée inconditionnel qui conserve le membre et la classe qui le contient, même si la classe n'est jamais instanciée, tandis quekeepannoexprime l'accessibilité conditionnelle.
Pour utiliser les annotations R8 keepanno dans un module Soong :
Ajoutez
"keepanno-annotations"àlibsdansAndroid.bp:libs: [ "keepanno-annotations", ],Importez
com.android.tools.r8.keepanno.annotations.*et annotez la déclaration ou le site d'appel dans le code source Java ou Kotlin :import com.android.tools.r8.keepanno.annotations.KeepItemKind; import com.android.tools.r8.keepanno.annotations.UsedByReflection; public final class CustomPluginController { @UsedByReflection( description = "Instantiated via Class.forName from plugin config", kind = KeepItemKind.CLASS_AND_METHODS) public CustomPluginController(Context context) { // ... } }R8 traduit les annotations
keepannodirectement dans son modèle de règle de conservation interne et supprime les annotations de la sortie DEX finale, sans ajouter de surcharge de bytecode d'exécution.
Éviter les erreurs courantes
Les sections suivantes décrivent les pièges courants liés à proguard.flags et keep.xml dans le code de la plate-forme et comment les résoudre.
Règles générales de conservation des composants
# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
- Pourquoi cela nuit aux performances : cette règle est complètement redondante avec AAPT2.
Comme elle utilise un caractère générique sans vérifier si le composant est enregistré dans le fichier
AndroidManifest.xmlfusionné final, elle force R8 à conserver chaque sous-classeActivity,ServiceouBroadcastReceivertrouvée n'importe où dans le chemin de classe, y compris les composants de bibliothèque inutilisés et les activités de débogage désactivées. - Correction recommandée : supprimez la règle. AAPT2 inspecte le fichier manifeste fusionné et génère des règles
-keepexactes pour les composants enregistrés.
Règles de conservation générales pour les gestionnaires de clics XML
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- Pourquoi cela nuit aux performances : la conservation de chaque méthode
public void *(View)dans chaque classe du module empêche R8 de supprimer ou d'intégrer toute méthode qui accepte un seul paramètreView. - Correction recommandée : supprimez la règle. AAPT2 analyse les fichiers XML de mise en page et génère des règles de conservation ciblées pour les méthodes référencées par les attributs
android:onClick.
Règles de conservation des getters et setters de View généraux
# Don't do this:
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
public *** get*();
}
- Pourquoi cela nuit aux performances : cette règle force R8 à conserver chaque getter et setter dans chaque sous-classe
Viewde l'application et de toutes les bibliothèques associées (y compris les bibliothèques AndroidX et Material), ce qui empêche la suppression des méthodes mortes et l'intégration dans le code de l'interface utilisateur. - Correction recommandée : supprimez la règle. AAPT2 conserve déjà les constructeurs des classes
Viewpersonnalisées gonflées à partir de fichiers XML de mise en page. Si le code anime une propriété de vue à l'aide de noms de chaînes réfléchissants tels queObjectAnimator.ofFloat(view, "translationZ", ...), remplacez le nom de la chaîne par une référence de propriété typée (View.TRANSLATION_Zou une implémentationFloatPropertyouIntPropertypersonnalisée) pour éviter complètement la réflexion. Si l'accès aux propriétés réfléchissantes ne peut pas être évité, annotez le getter ou le setter spécifique avec@UsedByReflection.
Désactivation de l'optimisation ou de l'obscurcissement généralisés
# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
- Pourquoi cela nuit aux performances : placer
-dontoptimizeou-dontshrinkdans un fichier.flagsremplace silencieusement les paramètresAndroid.bpdu module et désactive les passes d'optimisation sur l'ensemble de la cible. Pire encore, si une bibliothèque exporte un fichier.flagscontenant-dontoptimize, elle désactive l'optimisation R8 pour chaqueandroid_appen aval qui associe la bibliothèque. - Correction recommandée : Supprimez
-dontoptimize,-dontshrinket-dontobfuscatedes fichiers.flags. Contrôlez explicitement le comportement d'optimisation dansAndroid.bpà l'aide du blocoptimize(shrink,optimize,obfuscate) sur la cible feuille.
Indicateurs de diagnostic dans les règles validées
# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
- Pourquoi cela nuit à la compilation : les indicateurs de diagnostic inondent les journaux de compilation ou tentent d'écrire des fichiers de sortie dans des chemins d'accès locaux lors des compilations Soong en bac à sable.
- Correction recommandée : Supprimez ces options des fichiers
.flagsvalidés. Soong écrit automatiquement les sorties de mappage et d'utilisation de R8, telles queproguard_dictionaryetproguard_usage.zip, dans le répertoire intermédiaire du module sousout/soong/.intermediates/.
Règles de conservation en production pour le code de test
- Pourquoi cela nuit aux performances : l'ajout de règles
-keeppersonnalisées uniquement pour l'accès aux tests force ce code à rester non obscurci et à être conservé dans les builds de production. Bien que la configuration detrace_references_from(ou deallow_traced_referencesavectrace_references: true) ou l'annotation de méthodes avec@VisibleForTestingévite de gérer manuellement les règles-keepet ne conserve que les éléments référencés par les tests, ces symboles sont toujours inclus dans le binaire de production. - Correction recommandée : Éliminez les règles
-keepde test personnalisées en activanttrace_references_from(ouallow_traced_referencesettrace_references: true), ou structurez l'application dans une bibliothèque.implassociée à un APK de test d'auto-instrumentation à l'aide destatic_libspour que les points d'entrée réservés aux tests ne soient pas inclus dans l'APK de production, comme décrit à l'étape 2 : Migrer les règles de conservation basées sur les tests.
Caractères génériques de ressources globaux dans keep.xml
<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/*,@drawable/*,@string/*" />
- Pourquoi cela nuit aux performances : les caractères génériques généraux conservent toutes les ressources du type correspondant dans le module et ses dépendances, ce qui annule la réduction des ressources (
shrink_resources: true) et augmente la taille de l'APK et de la tableresources.arscmappée. - Correction recommandée :
- Privilégiez les références de ressources statiques à
keep.xml: évitez d'utiliser la méthodeResources.getIdentifierpour rechercher de manière dynamique un ensemble limité de ressources, telles que des chaînes de test numérotées ou des drawables thématiques. Utilisez plutôt une instructionswitchau moment de la compilation ou mappez sur des constantes statiquesR.id,R.stringouR.drawable. Les références statiques permettent à R8 et AAPT2 de tracer les ressources actives exactes, d'éliminer la surcharge de recherche de chaînes d'exécution et de supprimer complètement le besoin dekeep.xml. - Répertorier des noms de ressources spécifiques : si une recherche dynamique est requise, par exemple par un lecteur de licence tiers, répertoriez les identifiants de ressources exacts dans
tools:keep(par exemple,tools:keep="@raw/third_party_licenses"). - Supprimer les fichiers
keep.xmlredondants : si les ressources listées sont déjà référencées de manière statique dans le code (R.raw.foo) ou le code XML (@raw/foo), l'outil de réduction les conserve automatiquement. Vous pouvez donc supprimerres/raw/keep.xml.
- Privilégiez les références de ressources statiques à
Vérifier et auditer les modifications apportées aux règles
Chaque fois que vous supprimez ou limitez des règles de conservation dans proguard.flags ou keep.xml, vérifiez que le module se compile correctement, qu'il réussit les tests unitaires et d'instrumentation, et qu'il conserve tous les points d'entrée requis.
Modules de compilation et de test
Effectuez une compilation propre du module pour vérifier que R8 et AAPT2 se terminent sans avertissement de référence manquante :
m <MODULE_NAME>Exécutez des tests unitaires et d'instrumentation pour le module à l'aide de
atest:atest <TEST_MODULE_NAME>Pour les applications système, les services privilégiés ou les modules dépendant du matériel, exécutez des tests d'instrumentation et d'interface utilisateur sur les appareils physiques cibles ou dans votre laboratoire de test d'appareils pour tester les chemins de réflexion d'exécution, les liaisons IPC et l'inflation des ressources.
Inspecter les différences entre les fichiers DEX et les ressources
Comparez l'APK ou le JAR compilé avant et après les modifications de règles pour vérifier que R8 supprime le code et les ressources inutilisés sans supprimer les points d'entrée attendus :
Utilisez
apkanalyzeroudexdumppour inspecter les classes, méthodes et champs conservés dans les fichiers APK ou DEX de sortie :apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkLorsque vous modifiez
keep.xmlou activezshrink_resources, utilisezaapt2 dump resourcespour vérifier que la compilation supprime les ressources inutilisées et conserve celles requises :aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkLorsque vous exécutez
prebuilts/r8/r8.jardirectement sur leclasses.jarpré-R8 d'un module pour vérifier les modifications d'indicateurs hors connexion sans compilation complète de la plate-forme, transmettez les fichiers JAR de chemin de classe--libdu module (android.jar,framework.jaretlibspartagé) et les règlesproguard.optionsgénérées par AAPT2 à partir deAndroidManifest.xmlet des fichiers XML de mise en page. Sans ces entrées, R8 ne peut pas résoudre les super-types du framework (Activity,Service,Phone,Handler) et ne conserve pas suffisamment les méthodes de hiérarchie.
Analyser le rayon de conservation et la subsomption des règles avec R8
Le compilateur R8 Open Source inclut un analyseur Keep Radius (également exposé dans Android Studio et Gradle en tant qu'analyseur de configuration R8) qui mesure l'impact exact de chaque règle de conservation pendant la compilation. Soong intègre cet analyseur directement dans la compilation de la plate-forme Android (configurée dans build/soong/java/dex.go).
Pour analyser les règles de conservation d'un module de plate-forme ou d'un build entier :
Exécutez la compilation avec la variable d'environnement
R8_DUMP_KEEP_RADIUS=true:R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>Lorsque
R8_DUMP_KEEP_RADIUS=trueest défini, Soong demande à R8 d'enregistrer les métriques des règles de conservation dans un fichierr8keepradius.pbintermédiaire pour chaque module compilé sousout/soong/.intermediates/. Pour chaque règle de conservation et annotationkeepanno, R8 enregistre :- Rayon de conservation immédiat : classes, champs et méthodes exacts conservés par la règle, ainsi que les contraintes spécifiques qu'elle applique contre la réduction, l'optimisation ou l'obscurcissement.
- Subsomption de règles : quelles autres règles ou annotations de conservation conservent déjà les mêmes éléments. Si une règle personnalisée est complètement subsumée par une règle AAPT2 ou une règle de référence globale, vous pouvez la supprimer en toute sécurité.
- Règles globales et à l'échelle du package : règles qui utilisent des caractères génériques larges à l'échelle du package ou qui appliquent des directives de configuration globales.
Convertissez la sortie
r8keepradius.pben rapport HTML interactif à l'aide deKeepRadiusHtmlReportGenerator(fourni dansprebuilts/r8/r8.jar) :# Generate an HTML report for a single module INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME> java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ $INTERMEDIATES/android_common/r8keepradius.pb \ keep_radius_report.html # Scan all built modules under out/soong/.intermediates and generate # per-module HTML reports plus an aggregate keepradius.html summary java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ out/soong/.intermediates \ out/keep_radius_reportsLorsqu'un répertoire est fourni,
KeepRadiusHtmlReportGeneratorparcourt tous les fichiers*keepradius*.pb, génère un rapport HTML pour chaque module et créeout/keep_radius_reports/keepradius.htmlrésumant le nombre d'éléments actifs, le nombre d'éléments conservés et les règles de conservation de rayon le plus élevé dans la compilation. Pour en savoir plus sur l'interprétation des scores de minification, d'optimisation et d'obscurcissement dans le rapport généré, consultez Utiliser l'analyseur de configuration R8.
Soong est également compatible avec d'autres variables d'environnement de diagnostic et de boucle interne R8 dans build/soong/java/dex.go :
R8_DUMP_INPUT=true: écritr8inputs.zipdans le répertoire intermédiaire du module contenant tous les fichiers JAR d'entrée, les fichiers JAR de bibliothèque et les configurations ProGuard fusionnées pour la reproduction autonome de R8.R8_DUMP_PERFETTO_TRACE=true: écritr8trace.ptracedans le répertoire intermédiaire du module pour inspecter les passes de compilation R8 dans Perfetto.SOONG_SKIP_TRACE_REFERENCES=true: ignore l'exécution des règlestracereferencesconfigurées partrace_references_fromoutrace_referencespour accélérer les compilations de boucle interne locales lors de l'itération sur le code de test.
Valider les règles des consommateurs de bibliothèques
Lorsqu'un java_library ou un android_library exporte des règles de conservation vers des consommateurs en aval (export_proguard_flags_files: true), ces règles ne doivent pas inclure d'indicateurs globaux qui modifient ou désactivent l'optimisation pour l'application consommatrice.
R8 fournit un analyseur de règles de conservation Open Source (ProcessKeepRules), exposé dans la compilation de la plate-forme Android en tant qu'outil hôte process-keep-rules (défini dans prebuilts/r8/Android.bp) :
# Build the R8 keep rules validator host binary
m process-keep-rules
# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>
L'outil process-keep-rules analyse chaque fichier de configuration et échoue en affichant des diagnostics de fichier et de ligne s'il rencontre des directives non autorisées dans les règles d'utilisation de la bibliothèque. Les directives non autorisées incluent la désactivation de l'optimisation globale, de la réduction ou de l'obscurcissement (comme -dontoptimize ou -dontshrink), les indicateurs de reconditionnement de package et de modification d'accès, les indicateurs de diagnostic ou de mappage, et -keepattributes au niveau de l'application.
Auditer les arborescences sources avec pgaudit.py
Pour auditer les répertoires sources non compilés avec les analyseurs de compilation de R8, l'arborescence de la plate-forme Android inclut le script pgaudit.py sous build/make/core/proguard/tools/. Cet outil d'analyse statique analyse les fichiers Android.bp, proguard.flags et keep.xml d'un dépôt pour signaler les règles déjà couvertes par AAPT2 ou les références de la plate-forme globale, les caractères génériques larges, les remplacements -dontoptimize et les règles de conservation réservées aux tests :
# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/
# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json
Les équipes de plate-forme et d'OEM peuvent combiner pgaudit.py pour un tri rapide de l'arborescence source, process-keep-rules pour valider les règles de bibliothèque exportées, et R8_DUMP_KEEP_RADIUS=true avec KeepRadiusHtmlReportGenerator pour mesurer le rayon de conservation exact de la classe, du champ et de la méthode de chaque règle d'un build.