Optimiser le code et les ressources de la plate-forme avec R8

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. Pour les bibliothèques statiques (android_library et java_library statique), Soong n'exécute pas R8 directement, mais utilise le bloc de propriétés 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'intégrité du système :

  • Empreinte plus petite de la partition système : R8 supprime les classes, méthodes et ressources inutilisées avant d'empaqueter les APK et les JAR sur /system, /system_ext, /product et /vendor.
  • Artefacts compilés plus petits : moins de méthodes DEX signifie des fichiers .odex et .vdex plus petits générés par dex2oat lors de la compilation au moment de la compilation ou sur l'appareil.
  • Pression de mémoire d'exécution réduite : comme Android mappe les tables de ressources .odex, .vdex et APK dans la mémoire du processus à l'aide d'appels mmap à la pagination à la demande, les binaires plus petits réduisent les défauts de page majeurs lors du démarrage de l'application et diminuent la mémoire de code et de ressources résidente dans 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, d'élaguer les champs inutilisés et de propager les constantes dans les classes.

Configurer l'optimisation R8 dans Soong

Dans les fichiers Android.bp, configurez l'optimisation R8 à l'aide du bloc de propriétés 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, les champs et les méthodes inaccessibles. La valeur par défaut est true pour les cibles android_app (false pour les modules de test et java_library autonomes, qui transmettent -dontshrink sauf si elle est explicitement définie).
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ées par l'indicateur de compilation de version RELEASE_R8_OPTIMIZE_BY_DEFAULT).
obfuscate Contrôle la minification et le renommage des identifiants. La valeur par défaut est false pour la compatibilité historique, mais définissez-la sur true chaque fois que possible pour réduire la taille DEX et débloquer des optimisations plus poussées (voir Activer l'obscurcissement chaque fois que possible).
shrink_resources Supprime les ressources inutilisées (res/ entrées) 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 versions standard de la plate-forme).
proguard_flags_files Liste 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 des annotations ou des valeurs par défaut standards suffisent.
export_proguard_flags_files Propage le proguard_flags_files de cette bibliothèque aux modules en aval qui en dépendent de manière statique.
trace_references_from Liste les cibles associées de la bibliothèque Java dont les références de bytecode dans cette cible sont automatiquement suivies et conservées par R8.

Activer l'obscurcissement chaque fois que possible

Dans Soong, obfuscate est défini par défaut sur false pour la 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 .vdex plus petite : 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_ids et string_data_item) et les descripteurs de type. Étant donné qu'Android mappe les fichiers .vdex et .odex dans 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é d'exécution.
  • Optimisations plus approfondies 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 normalement ignorer pour éviter les conflits de noms.
  • Symbolisation complète de la trace de 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_dictionary dans le répertoire intermédiaire du module, regroupe tous les dictionnaires de modules dans l'artefact proguard-dict.zip de compilation, intègre un hachage de mappage (--map-id-template) dans l'en-tête DEX et réécrit les attributs de classe SourceFile (--source-file-template) afin que retrace puisse symboliser automatiquement les traces de pile.

Ne conservez obfuscate: false (ou conservez explicitement les noms d'API publiques) que pour :

  • Bibliothèques partagées sur le bootclasspath ou le classpath system_server : les modules (java_library ou java_sdk_library) qui exposent une API externe et qui sont liés dynamiquement par d'autres modules au moment de l'exécution (tels que les cibles framework.jar, services.jar ou <uses-library>) doivent conserver obfuscate: false ou préserver explicitement leur surface d'API à l'aide des règles @KeepForApi ou -keep (ainsi que protect_api_surface: true pour les cibles bootclasspath). Les appelants compilés par rapport à leurs stubs peuvent ainsi résoudre les noms de classes et de membres au moment de l'exécution.

Avant d'activer obfuscate: true sur une application, vérifiez les conditions préalables à la migration suivantes :

  • Dépendances externes de l'APK de test : si un APK android_test externe définit instrumentation_for: "MyApp" et appelle directement des classes ou des méthodes internes de MyApp, le fait de renommer ces symboles entraîne NoSuchMethodError ou NoClassDefFoundError lors de l'exécution du test. Il est préférable de structurer le test en tant qu'APK auto-instrumenté liant MyApp.impl de manière statique, d'annoter les hooks de test avec @VisibleForTesting ou de configurer trace_references_from (voir Étape 2 : Migrer les règles Keep axé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, getDeclaredMethod ou JNI FindClass et GetMethodID) sans règles ni annotations de conservation, l'obscurcissement renomme ces cibles et interrompt les recherches d'exécution. (Notez que la réflexion non annotée échoue également sous shrink: true et optimize: true.) Annotez ces points d'entrée avec le guide sur les annotations à conserver (@UsesReflection, @UsedByReflection ou @UsedByNative) afin que R8 conserve leurs noms et obscurcisse le reste du module.

Suivez le principe de l'absence d'indicateurs personnalisés

Le module de plate-forme Android idéal ne comporte aucun fichier proguard.flags ni keep.xml personnalisé. 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 JNI native et @VisibleForTesting) ou générés par AAPT2 à partir de AndroidManifest.xml et 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, préférez les annotations de site de déclaration (keepanno, @VisibleForTesting), les règles de bibliothèque exportées (export_proguard_flags_files: true) ou le linking de test statique aux fichiers .flags détachés (consultez Auditer et migrer les règles Keep 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 de base 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 base globales suivantes à chaque invocation R8 sur les cibles de compilation DEX (android_app, android_test et modules 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.VisibleForTesting dans tous les packages, et avec @VisibleForTesting (androidx.annotation.VisibleForTesting ou com.google.common.annotations.VisibleForTesting) dans les packages android.**, com.android.** et com.google.android.**. Préserve également @TestApi, @Keep (androidx.annotation, android.support.annotation, com.android.internal.annotations), @KeepForWeakReference, @WeaklyReferencedCallback et @dalvik.annotation.optimization.**.
  • build/make/core/proguard_basic_keeps.flags : conserve les attributs SourceFile pour les traces de pile, les annotations de visibilité d'exécution (attributs RuntimeVisible*Annotations), les attributs Exceptions et AnnotationDefault, les méthodes native, les membres Serializable, les méthodes @JavascriptInterface, les constructeurs Throwable(String), les champs Parcelable$CREATOR et les champs MessageLite protobuf.
  • build/make/core/proguard/kotlin.flags : désactive les avertissements inoffensifs pour des méta-annotations Kotlin spécifiques (kotlin.Metadata et kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) et supprime les annotations DebugMetadata de Kotlin dans les versions Release.
  • build/make/core/proguard/checknotnull.flags : remplace les appels d'assistance courants pour la vérification des valeurs null (com.google.common.base.Preconditions.checkNotNull et dagger.internal.Preconditions.checkNotNull*) par des vérifications concises des valeurs null du bytecode. Ce fichier omet intentionnellement Objects.requireNonNull pour préserver les messages d'exception explicites au-delà des limites de l'API du framework.
  • build/make/core/proguard/enumvalues.flags : conserve les méthodes values et valueOf sur les types enum, 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 XML AndroidManifest.xml, de mise en page et de préférences fusionnés pour générer des règles keep exactes pour chaque méthode Activity, Service, BroadcastReceiver, ContentProvider, BackupAgent, Application, sous-classe View personnalisée, sous-classe Preference et XML android:onClick enregistrée.

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 déjà couvertes par les règles de base globales, les règles de mise en page et de fichier manifeste AAPT2, ou les paramètres par défaut 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 retirez proguard_flags_files de Android.bp. Si le bloc optimize restant ne fait que réaffirmer les paramètres par défaut, supprimez-le et mettez en forme le fichier build avec bpfmt -w Android.bp. Lorsque vous nettoyez un package, vérifiez si les modules associés dans les sous-répertoires (tels que les variantes cibles Kotlin) font référence au même fichier d'indicateurs et mettez-les à jour ensemble.

Étape 2 : Migrer les règles de conservation axées sur les tests

Déplacez les règles de conservation axées sur les tests hors des fichiers .flags de production personnalisés à l'aide de l'un des modèles suivants :

  • Associez la bibliothèque d'implémentation aux tests (static_libs) : lorsqu'un android_test exerce des détails d'implémentation internes ou privés à un package d'une application, l'architecture de plate-forme recommandée consiste à placer les fichiers sources de l'application dans un android_library (MyApp.impl) et à associer statiquement MyApp.impl à MyApp et MyAppTests :

    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 à MyAppTests de s'exécuter en tant que test d'auto-instrumentation avec un accès complet aux classes internes, permet à MyApp d'activer obfuscate: true librement sans interrompre les tests, évite d'expédier des points d'entrée réservés aux tests dans l'APK de production et élimine la reconstruction de MyApp lorsque le code de test change.

  • Annoter les hooks de test avec @VisibleForTesting : si un APK de test externe appelle un petit nombre de méthodes ou de constructeurs internes sur une cible android_app et que la restructuration en bibliothèque .impl n'est pas pratique, annotez ces déclarations avec @VisibleForTesting. La référence globale build/make/core/proguard.flags conserve automatiquement les éléments @VisibleForTesting dans les packages android.**, com.android.** et com.google.android.** sans fichiers .flags personnalisés. (Pour les modules de fournisseur en dehors de ces espaces de noms, utilisez @UsedByReflection ou les règles de bibliothèque exportées.)

  • Utilisez trace_references_from au-delà des limites de la bibliothèque multiplate-forme : lorsque les tests ne peuvent pas lier statiquement l'implémentation cible (par exemple, les tests exerçant des services de serveur système ou des fichiers JAR de plate-forme tels que framework-connectivity), configurez trace_references_from sur le module cible en pointant vers un module complémentaire de bibliothèque Java contenant les sources de test. R8 suit et conserve toutes les classes et tous les membres référencés par le bytecode associé.

É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 ses propres rappels de réflexion interne ou 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 statiquement 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 de manière propre au-delà des limites des packages. Pour obtenir des conseils généraux sur la création de règles de conservation des consommateurs de bibliothèque 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 sont accessibles par réflexion ou JNI et ne sont pas couverts par AAPT2 ni par les références globales, remplacez les règles -keep détachées dans les fichiers .flags par des annotations de source issues du Guide to Keep Annotations (consultez la documentation Javadoc keepanno) :

  • @UsesReflection : préférez cette annotation lorsque vous contrôlez le code effectuant la réflexion. Placez-le au niveau de l'appel de réflexion pour déclarer les classes, méthodes ou champs cibles auxquels vous accédez de manière dynamique. Étant donné que @UsesReflection encode automatiquement une précondition selon laquelle le site d'appel annoté lui-même est accessible, R8 supprime à la fois l'appelant et la cible de réflexion si le site d'appel n'est pas utilisé.
  • @UsedByReflection : à placer sur les classes, les méthodes, les champs ou les constructeurs qui sont instanciés ou appelés de manière réflexive par du code ou des bibliothèques externes, comme les classes chargées avec Class.forName à partir des clés Bundle ou Settings, ou les classes de plug-in chargées dans des chargeurs de classe dynamiques. Spécifiez kind (par exemple, KeepItemKind.CLASS_AND_METHODS), preconditions (@KeepCondition) et les contraintes de paramètres 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 @UsedByReflection aux composants enregistrés dans le fichier manifeste, tels que JobService ou BroadcastReceiver, car AAPT2 les conserve déjà.
  • @UsedByNative : à placer sur les méthodes ou les champs auxquels le code JNI C ou C++ accède à l'aide de GetMethodID, GetStaticMethodID ou GetFieldID.
  • @KeepForApi : à placer sur les classes ou les membres de l'API de la bibliothèque qui doivent rester intacts lorsqu'une bibliothèque elle-même est réduite avant distribution.
  • @Keep (androidx.annotation.Keep) : à utiliser comme solution de repli lorsque keepanno n'est pas applicable. L'application de @Keep à une classe conserve la classe et tous ses membres de manière inconditionnelle. L'application de @Keep à une méthode ou à un champ agit comme un point d'entrée inconditionnel qui conserve le membre et sa classe conteneur, même si la classe n'est jamais instanciée, tandis que keepanno exprime l'accessibilité conditionnelle.

Pour utiliser les annotations keepanno de R8 dans un module Soong :

  1. Ajoutez "keepanno-annotations" à libs dans Android.bp :

    libs: [
        "keepanno-annotations",
    ],
    
  2. 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 keepanno directement dans son modèle interne de règles de conservation et supprime les annotations de la sortie DEX finale, sans ajouter de surcharge de bytecode d'exécution.

Éviter les pièges courants

Les sections suivantes décrivent les pièges fréquents de proguard.flags et keep.xml dans le code de la plate-forme, et comment les corriger.

Règles de conservation des composants généraux

# 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-il aux performances ? Cette règle est totalement redondante avec AAPT2. Comme il utilise un caractère générique sans vérifier si le composant est enregistré dans le AndroidManifest.xml fusionné final, il force R8 à conserver chaque sous-classe Activity, Service ou BroadcastReceiver trouvée n'importe où dans le classpath, 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 -keep exactes pour les composants enregistrés.

Règles de conservation générales pour le gestionnaire de clics XML

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • Pourquoi cela nuit-il 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ètre View.
  • 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.

Conserver les règles pour les getters et setters Broad View

# 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-il aux performances ? Cette règle force R8 à conserver chaque getter et setter dans chaque sous-classe View de l'application et dans toutes les bibliothèques associées (y compris les bibliothèques AndroidX et Material). Cela bloque la suppression des méthodes inutilisées et l'intégration du code de l'UI.
  • Correction recommandée : supprimez la règle. AAPT2 préserve déjà les constructeurs pour les classes View personnalisé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éflexifs tels que ObjectAnimator.ofFloat(view, "translationZ", ...), remplacez le nom de chaîne par une référence de propriété typée (View.TRANSLATION_Z ou une implémentation FloatProperty ou IntProperty personnalisée) pour éviter complètement la réflexion. Si l'accès aux propriétés par réflexion 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-il aux performances ? : placer -dontoptimize ou -dontshrink dans un fichier .flags remplace silencieusement les paramètres Android.bp du module et désactive les passes d'optimisation pour l'ensemble de la cible. Pire encore, si une bibliothèque exporte un fichier .flags contenant -dontoptimize, elle désactive l'optimisation R8 pour chaque android_app en aval qui associe la bibliothèque.
  • Solution recommandée : Supprimez -dontoptimize, -dontshrink et -dontobfuscate des fichiers .flags. Contrôlez explicitement le comportement d'optimisation dans Android.bp à l'aide du bloc optimize (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-il à 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.
  • Solution recommandée : Supprimez ces indicateurs des fichiers .flags validés. Soong écrit automatiquement les sorties de mappage et d'utilisation R8, telles que proguard_dictionary et proguard_usage.zip, dans le répertoire intermédiaire du module sous out/soong/.intermediates/.

Règles de conservation de la production pour le code de test

  • Pourquoi cela nuit aux performances : l'ajout de règles -keep personnalisées uniquement pour l'accès aux tests oblige ce code à rester non obscurci et conservé dans les builds de production. Bien que l'annotation des méthodes avec @VisibleForTesting ou la configuration de trace_references_from évitent de gérer manuellement les règles -keep, ces symboles sont toujours inclus dans le binaire de production.
  • Solution recommandée : Pour que les points d'entrée réservés aux tests soient complètement exclus de l'APK de production, structurez l'application dans une bibliothèque .impl et associez-la à un APK de test d'auto-instrumentation à l'aide de static_libs, comme décrit dans Étape 2 : Migrer les règles Keep axées sur les tests.

Caractères génériques de ressources globales 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-il aux performances ? Les caractères génériques globaux 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 table resources.arsc mappée.
  • Correction recommandée :
    • Préférez les références de ressources statiques à keep.xml : évitez d'utiliser la méthode Resources.getIdentifier pour rechercher dynamiquement un ensemble limité de ressources, telles que des chaînes de test numérotées ou des éléments drawable thématiques. Utilisez plutôt une instruction switch au moment de la compilation ou mappez des constantes statiques R.id, R.string ou R.drawable. Les références statiques permettent à R8 et AAPT2 de suivre les ressources en direct exactes, d'éliminer la surcharge de recherche de chaînes d'exécution et de supprimer complètement le besoin de keep.xml.
    • Lister des noms de ressources spécifiques : si une recherche dynamique est requise, par exemple par un lecteur de licence tiers, listez les identifiants de ressources exacts dans tools:keep (par exemple, tools:keep="@raw/third_party_licenses").
    • Supprimez les fichiers keep.xml redondants : si les ressources listées sont déjà référencées de manière statique dans le code (R.raw.foo) ou le fichier XML (@raw/foo), l'outil de réduction les conserve automatiquement. Vous pouvez donc supprimer res/raw/keep.xml.

Vérifier et auditer les modifications apportées aux règles

Chaque fois que vous supprimez ou affinez 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.

Créer et tester des modules

  1. Compilez le module de manière propre pour vérifier que R8 et AAPT2 se terminent sans avertissements de référence manquante :

    m <MODULE_NAME>
    
  2. Exécutez des tests unitaires et d'instrumentation pour le module à l'aide de atest :

    atest <TEST_MODULE_NAME>
    
  3. Pour les applications système, les services privilégiés ou les modules dépendants du matériel, exécutez des tests d'instrumentation et d'UI sur des appareils physiques cibles ou dans votre laboratoire de tests d'appareils pour exercer 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és avant et après les modifications des règles pour confirmer que R8 supprime le code et les ressources inutilisés sans supprimer les points d'entrée attendus :

  • Utilisez apkanalyzer ou dexdump pour 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>.apk
    
  • Lorsque vous modifiez keep.xml ou activez shrink_resources, utilisez aapt2 dump resources pour vérifier que la compilation supprime les ressources inutilisées et conserve celles qui sont requises :

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    

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 sous le nom R8 Configuration Analyzer) 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 pour un module de plate-forme ou pour l'ensemble d'une compilation :

  1. 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=true est défini, Soong demande à R8 d'enregistrer les métriques de règles keep dans un fichier r8keepradius.pb intermédiaire pour chaque module compilé sous out/soong/.intermediates/. Pour chaque règle de conservation et annotation keepanno, R8 enregistre les informations suivantes :

    • Rayon de conservation immédiate : 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 : autres règles ou annotations de conservation qui conservent déjà les mêmes éléments. Si une règle personnalisée est entièrement englobée par une règle AAPT2 ou une règle de référence globale, vous pouvez la supprimer sans risque.
    • Règles globales et à l'échelle du package : règles qui utilisent des caractères génériques à l'échelle du package ou qui appliquent des directives de configuration globales.
  2. Convertissez la sortie r8keepradius.pb en rapport HTML interactif à l'aide de KeepRadiusHtmlReportGenerator (fourni avec prebuilts/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_reports
    

    Lorsqu'un répertoire est fourni, KeepRadiusHtmlReportGenerator parcourt tous les fichiers *keepradius*.pb, génère un rapport HTML pour chaque module et crée out/keep_radius_reports/keepradius.html récapitulant le nombre d'éléments actifs, le nombre d'éléments conservés et les règles de conservation avec le 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 accepte également deux variables d'environnement de diagnostic R8 supplémentaires dans build/soong/java/dex.go :

  • R8_DUMP_INPUT=true : écrit r8inputs.zip dans 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 R8 autonome.
  • R8_DUMP_PERFETTO_TRACE=true : écrit r8trace.ptrace dans le répertoire intermédiaire du module pour inspecter les passes de compilation R8 dans Perfetto.

Valider les règles de consommateur de bibliothèque

Lorsqu'une exportation java_library ou android_library conserve des règles pour les 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 avec des diagnostics de fichier et de ligne s'il rencontre des directives non autorisées dans les règles des consommateurs de bibliothèque. Les directives interdites incluent les directives de désactivation de l'optimisation globale, de la réduction ou de l'obscurcissement (telles que -dontoptimize ou -dontshrink), les indicateurs de reconditionnement et de modification de l'accès aux packages, 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 en même temps que les analyseurs au moment de la 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 plate-forme globales, 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 rétention exact des classes, des champs et des méthodes de chaque règle dans une build.