Réduire, obscurcir et optimiser votre application

Si vous souhaitez réduire au maximum la taille de votre application, vous devez activer la minification dans votre build afin de supprimer les ressources et le code inutilisés. Lorsque vous activez la minification, vous bénéficiez également de l'obscurcissement, une technique qui raccourcit les noms des classes et des membres de votre application, ainsi que de l'optimisation, qui applique des stratégies plus agressives afin de réduire davantage encore la taille de votre application. Cette page vous explique comment R8 effectue ces tâches au moment de la compilation et vous montre comment les personnaliser.

Lorsque vous compilez votre projet à l'aide du plug-in Android Gradle 3.4.0 ou version ultérieure, ProGuard n'est plus utilisé pour optimiser le code au moment de la compilation. Le plug-in utilise, à la place, le compilateur R8 pour gérer les tâches suivantes :

  • Minification de code (ou tree shaking) : détecte et supprime en toute sécurité les classes, champs, méthodes et attributs inutilisés de votre application et de ses dépendances de bibliothèque, ce qui en fait un outil très utile pour contourner la limite de 65 536 références. Par exemple, si vous n'utilisez que quelques API d'une dépendance de bibliothèque, la minification permet d'identifier le code de bibliothèque que votre application n'utilise pas et de ne supprimer que ce code de votre application. Pour en savoir plus, consultez la section sur la minification du code.
  • Réduction des ressources : supprime les ressources inutilisées de votre application empaquetée, y compris celles qui se trouvent dans les dépendances de bibliothèque de votre application. Cette tâche fonctionne conjointement avec la minification de code, de sorte qu'une fois le code inutilisé supprimé, toutes les ressources qui ne sont plus référencées peuvent également être supprimées en toute sécurité. Pour en savoir plus, consultez la section sur la réduction des ressources.
  • Obscurcissement : raccourcit le nom des classes et des membres, ce qui réduit la taille des fichiers DEX. Pour en savoir plus, consultez la section sur l'obscurcissement du code.
  • Optimisation : inspecte et réécrit votre code pour réduire davantage encore la taille des fichiers DEX de votre application. Par exemple, si le compilateur R8 détecte que la branche else {} d'une instruction if/else donnée n'est jamais utilisée, il supprime le code de la branche else {}. Pour en savoir plus, consultez la section sur l'optimisation du code.

Lorsque vous compilez la version finale de votre application, R8 peut être configuré pour effectuer les tâches au moment de la compilation décrites ci-dessus. Vous pouvez également désactiver certaines tâches ou personnaliser le comportement de R8 via des fichiers de règles ProGuard. En fait, R8 fonctionne avec tous vos fichiers de règles ProGuard existants. Par conséquent, la mise à jour du plug-in Android Gradle en vue d'utiliser R8 ne devrait pas vous obliger à modifier vos règles existantes.

Activer la minification, l'obscurcissement et l'optimisation

Si vous utilisez Android Studio 3.4 ou le plug-in Android Gradle 3.4.0 ou version ultérieure, R8 est le compilateur par défaut qui convertit le bytecode Java de votre projet au format DEX exécuté sur la plate-forme Android. Cependant, lorsque vous créez un projet à l'aide d'Android Studio, la minification, l'obscurcissement et l'optimisation du code ne sont pas activés par défaut. En effet, ces optimisations augmentent la durée de compilation de votre projet et peuvent introduire des bugs si vous ne personnalisez pas suffisamment le code à conserver.

Il est donc préférable d'activer ces tâches lors de la compilation de la version finale de votre application que vous testez avant la publication. Pour activer la minification, l'obscurcissement et l'optimisation, incluez les éléments suivants dans votre script de compilation au niveau du projet.

Kotlin

android {
    buildTypes {
        getByName("release") {
            // Enables code shrinking, obfuscation, and optimization for only
            // your project's release build type. Make sure to use a build
            // variant with `isDebuggable=false`.
            isMinifyEnabled = true

            // Enables resource shrinking, which is performed by the
            // Android Gradle plugin.
            isShrinkResources = true

            // Includes the default ProGuard rules files that are packaged with
            // the Android Gradle plugin. To learn more, go to the section about
            // R8 configuration files.
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    ...
}

Groovy

android {
    buildTypes {
        release {
            // Enables code shrinking, obfuscation, and optimization for only
            // your project's release build type. Make sure to use a build
            // variant with `debuggable false`.
            minifyEnabled true

            // Enables resource shrinking, which is performed by the
            // Android Gradle plugin.
            shrinkResources true

            // Includes the default ProGuard rules files that are packaged with
            // the Android Gradle plugin. To learn more, go to the section about
            // R8 configuration files.
            proguardFiles getDefaultProguardFile(
                    'proguard-android-optimize.txt'),
                    'proguard-rules.pro'
        }
    }
    ...
}

Fichiers de configuration R8

R8 utilise les fichiers de règles ProGuard pour modifier son comportement par défaut et mieux comprendre la structure de votre application, comme les classes qui servent de points d'entrée dans le code de votre application. Bien que vous puissiez modifier certains de ces fichiers de règles, d'autres peuvent être générés automatiquement par des outils de compilation, comme AAPT2, ou être hérités des dépendances de la bibliothèque de votre application. Le tableau ci-dessous décrit les sources des fichiers de règles ProGuard utilisées par R8.

Source Position Description
Android Studio <module-dir>/proguard-rules.pro Lorsque vous créez un module à l'aide d'Android Studio, l'IDE crée un fichier proguard-rules.pro dans le répertoire racine de ce module.

Par défaut, ce fichier n'applique aucune règle. Vous devez donc inclure ici vos propres règles ProGuard telles que vos règles de conservation personnalisées.

Plug-in Android Gradle Généré par le plug-in Android Gradle au moment de la compilation. Le plug-in Android Gradle génère le fichier proguard-android-optimize.txt, qui inclut des règles utiles pour la plupart des projets Android et active les annotations @Keep*.

Par défaut, lorsque vous créez un module à l'aide d'Android Studio, le script de compilation au niveau du module inclut ce fichier de règles dans votre build.

Remarque : Le plug-in Android Gradle inclut des fichiers de règles ProGuard prédéfinis supplémentaires, mais nous vous recommandons d'utiliser proguard-android-optimize.txt.

Dépendances de bibliothèque Bibliothèques AAR : <library-dir>/proguard.txt

Bibliothèques JAR : <library-dir>/META-INF/proguard/

Si une bibliothèque AAR est publiée avec son propre fichier de règles ProGuard et que vous l'incluez en tant que dépendance au moment de la compilation, R8 applique automatiquement ses règles lors de la compilation de votre projet.

L'utilisation des fichiers de règles fournis avec les bibliothèques AAR s'avère utile si certaines règles de conservation sont nécessaires au bon fonctionnement de la bibliothèque ; en d'autres termes, le développeur de la bibliothèque a effectué les étapes de dépannage pour vous.

Cependant, vous devez savoir qu'en raison de leur additivité, certaines règles ProGuard incluses dans une dépendance de la bibliothèque AAR ne peuvent pas être supprimées et peuvent affecter la compilation d'autres parties de votre application. Par exemple, si une bibliothèque comprend une règle permettant de désactiver les optimisations de code, cette règle désactive les optimisations pour l'ensemble de votre projet.

Android Asset Packaging Tool 2 (AAPT2) Après avoir compilé votre projet avec minifyEnabled true : <module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt AAPT2 génère des règles de conservation en fonction des références aux classes du fichier manifeste de votre application, des mises en page et d'autres ressources d'application. Par exemple, AAPT2 inclut une règle de conservation pour chaque activité que vous enregistrez dans le fichier manifeste de votre application en tant que point d'entrée.
Fichiers de configuration personnalisés Par défaut, lorsque vous créez un module à l'aide d'Android Studio, l'IDE crée <module-dir>/proguard-rules.pro pour vous permettre d'ajouter vos propres règles. Vous pouvez inclure des configurations supplémentaires que R8 appliquera au moment de la compilation.

Lorsque vous définissez la propriété minifyEnabled sur true, R8 combine les règles provenant de toutes les sources disponibles répertoriées ci-dessus. Il est important de s'en souvenir lorsque vous résolvez des problèmes dans R8, car d'autres dépendances au moment de la compilation, telles que les dépendances de bibliothèque, peuvent modifier le comportement de R8 à votre insu.

Pour générer un rapport complet de toutes les règles appliquées par R8 lors de la compilation de votre projet, incluez le code suivant dans le fichier proguard-rules.pro de votre module :

// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt

Inclure des configurations supplémentaires

Lorsque vous créez un projet ou un module à l'aide d'Android Studio, l'IDE crée un fichier <module-dir>/proguard-rules.pro qui vous permet d'inclure vos propres règles. Vous pouvez également inclure des règles supplémentaires à partir d'autres fichiers en les ajoutant à la propriété proguardFiles dans le script de compilation de votre module.

Vous pouvez, par exemple, ajouter des règles spécifiques à chaque variante de compilation en ajoutant une autre propriété proguardFiles dans le bloc productFlavor correspondant. Le fichier Gradle suivant ajoute flavor2-rules.pro au type de produit flavor2. Désormais, flavor2 utilise les trois règles ProGuard, car celles du bloc release sont également appliquées.

De plus, vous pouvez ajouter la propriété testProguardFiles, qui spécifie une liste de fichiers ProGuard inclus uniquement dans l'APK test:

Kotlin

android {
    ...
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                // List additional ProGuard rules for the given build type here. By default,
                // Android Studio creates and includes an empty rules file for you (located
                // at the root directory of each module).
                "proguard-rules.pro"
            )
            testProguardFiles(
                // The proguard files listed here are included in the
                // test APK only.
                "test-proguard-rules.pro"
            )
        }
    }
    flavorDimensions.add("version")
    productFlavors {
        create("flavor1") {
            ...
        }
        create("flavor2") {
            proguardFile("flavor2-rules.pro")
        }
    }
}

Groovy

android {
    ...
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                // List additional ProGuard rules for the given build type here. By default,
                // Android Studio creates and includes an empty rules file for you (located
                // at the root directory of each module).
                'proguard-rules.pro'
            testProguardFiles
                // The proguard files listed here are included in the
                // test APK only.
                'test-proguard-rules.pro'
        }
    }
    flavorDimensions "version"
    productFlavors {
        flavor1 {
            ...
        }
        flavor2 {
            proguardFile 'flavor2-rules.pro'
        }
    }
}

Réduire votre code

La minification de code avec R8 est activée par défaut lorsque vous définissez la propriété minifyEnabled sur true.

La minification de code (également appelée tree shaking) est un processus qui consiste à supprimer le code qui, selon R8, n'est pas nécessaire au moment de l'exécution. Ce processus peut considérablement réduire la taille de votre application si, par exemple, elle inclut de nombreuses dépendances de bibliothèque, mais n'utilise qu'une petite partie de leurs fonctionnalités.

Pour réduire (ou minifier) le code de votre application, R8 détermine d'abord tous les points d'entrée dans le code de votre application en fonction de l'ensemble combiné de fichiers de configuration. Ces points d'entrée comprennent toutes les classes que la plate-forme Android peut utiliser pour ouvrir les activités ou services de votre application. À partir de chaque point d'entrée, R8 inspecte le code de votre application pour créer un graphique de toutes les méthodes, variables de membre et autres classes auxquelles votre application peut accéder au moment de l'exécution. Le code qui n'est pas relié à ce graphique est considéré comme inaccessible et peut être supprimé de l'application.

La figure 1 présente une application avec une dépendance de bibliothèque d'exécution. Lors de l'inspection du code de l'application, R8 détermine que les méthodes foo(), faz() et bar() sont accessibles à partir du point d'entrée MainActivity.class. Cependant, la classe OkayApi.class ou sa méthode baz() n'est jamais utilisée par votre application au moment de l'exécution. R8 supprime alors ce code lors de la réduction de votre application.

Figure 1 : Au moment de la compilation, R8 crée un graphique en fonction des règles de conservation combinées de votre projet afin d'identifier le code inaccessible.

R8 détermine les points d'entrée au moyen des règles -keep dans les fichiers de configuration R8 du projet. Autrement dit, les règles de conservation spécifient les classes que R8 ne doit pas supprimer lors de la réduction de votre application, et R8 considère ces classes comme des points d'entrée possibles dans votre application. Le plug-in Android Gradle et AAPT2 génèrent automatiquement les règles de conservation requises par la plupart des projets d'application, telles que les activités, les vues et les services de votre application. Cependant, si vous devez personnaliser ce comportement par défaut à l'aide de règles de conservation supplémentaires, consultez la section Personnaliser le code à conserver.

Si vous souhaitez uniquement réduire la taille des ressources de votre application, passez directement à la section traitant de la réduction des ressources.

Personnaliser le code à conserver

Dans la plupart des cas, le fichier de règles ProGuard par défaut (proguard-android- optimize.txt) est suffisant pour permettre à R8 de ne supprimer que le code inutilisé. Cependant, il est parfois difficile pour R8 d'effectuer une analyse correcte et il se peut qu'il supprime du code dont votre application a réellement besoin. Voici quelques exemples de cas où le code peut être supprimé par erreur :

  • Lorsque votre application appelle une méthode à partir de l'interface JNI (Java Native Interface)
  • Lorsque votre application recherche du code au moment de l'exécution (comme avec la réflexion)

Normalement, le test de votre application doit faire apparaître toutes les erreurs causées par du code supprimé de manière inappropriée. Vous pouvez également vérifier quel code a été supprimé en générant un rapport sur le code supprimé.

Pour corriger les erreurs et forcer R8 à conserver un certain code, ajoutez une ligne -keep dans le fichier de règles ProGuard. Par exemple :

-keep public class MyClass

Vous pouvez également ajouter l'annotation @Keep au code que vous souhaitez conserver. Ajouter @Keep à une classe permet de la conserver en entier, sans la modifier. Si vous ajoutez cette annotation à une méthode ou à un champ, cet élément (et son nom) ainsi que le nom de la classe seront conservés tels quels. Notez que cette annotation n'est disponible que lorsque vous utilisez la bibliothèque d'annotations AndroidX et que vous incluez le fichier de règles ProGuard fourni avec le plug-in Android Gradle, comme indiqué dans la section sur l'activation de la minification.

Il y a de nombreux facteurs à prendre en compte lorsque vous utilisez l'option -keep. Pour en savoir plus sur la personnalisation de votre fichier de règles, consultez le manuel de ProGuard. La section Dépannage décrit d'autres problèmes courants que vous pouvez rencontrer lorsque votre code est supprimé.

Supprimer des bibliothèques natives

Par défaut, les bibliothèques de code natives sont supprimées des builds de votre application. Cette opération consiste à supprimer la table des symboles et les informations de débogage contenues dans les bibliothèques natives utilisées par votre application. La suppression des bibliothèques de code natives entraîne une réduction considérable de la taille. Cependant, il est impossible de diagnostiquer les plantages sur la Google Play Console en raison d'informations manquantes (telles que les noms des classes et des fonctions).

Prise en charge des plantages natifs

La Google Play Console signale les plantages natifs sous Android Vitals. Il suffit de quelques étapes pour générer et importer un fichier de décodage natif pour votre application. Ce fichier active des traces de pile de plantage natif décodées (contenant les noms des classes et des fonctions) dans Android Vitals pour vous aider à déboguer votre application en production. Ces étapes varient en fonction de la version du plug-in Android Gradle utilisé dans votre projet et du résultat de compilation de votre projet.

Plug-in Android Gradle 4.1 ou version ultérieure

Si votre projet compile un Android App Bundle, vous pouvez y inclure automatiquement le fichier de décodage. Pour inclure ce fichier dans des builds, ajoutez le code suivant au fichier build.gradle.kts de votre application :

android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }

Sélectionnez l'un des niveaux de décodage suivants :

  • Utilisez SYMBOL_TABLE pour obtenir les noms des fonctions dans les traces de pile décodées de la Play Console. Ce niveau prend en charge les Tombstones.
  • Utilisez FULL pour obtenir les noms des fonctions, les fichiers et les numéros de ligne dans les traces de pile décodées de la Play Console.

Si votre projet compile un APK, utilisez le paramètre de compilation build.gradle.kts présenté précédemment pour générer séparément le fichier de décodage natif. Importez manuellement le fichier de décodage natif dans la Google Play Console. Dans le cadre du processus de compilation, le plug-in Android Gradle génère ce fichier à l'emplacement de projet suivant :

app/build/outputs/native-debug-symbols/variant-name/native-debug-symbols.zip

Plug-in Android Gradle 4.0 ou version antérieure (et autres systèmes de compilation)

Dans le cadre du processus de compilation, le plug-in Android Gradle conserve une copie des bibliothèques intactes dans un répertoire de projet. Cette structure de répertoire est semblable à ceci :

app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
├── arm64-v8a/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
├── x86/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
└── x86_64/
    ├── libgameengine.so
    ├── libothercode.so
    └── libvideocodec.so
  1. Compressez le contenu de ce répertoire :

    cd app/build/intermediates/cmake/universal/release/obj
    zip -r symbols.zip .
    
  2. Importez manuellement le fichier symbols.zip dans la Google Play Console.

Réduire vos ressources

La réduction des ressources ne fonctionne que parallèlement à la minification du code. Une fois que l'outil de minification de code a supprimé tout le code inutilisé, le réducteur de ressources peut identifier les ressources qui sont encore utilisées par l'application. Cela vaut surtout lorsque vous ajoutez des bibliothèques de code qui incluent des ressources. Vous devez supprimer le code de bibliothèque inutilisé afin que les ressources de la bibliothèque ne soient plus référencées et puissent donc être supprimées par le réducteur de ressources.

Pour activer la minification des ressources, définissez la propriété shrinkResources sur true dans votre script de compilation (avec minifyEnabled pour la minification du code). Par exemple :

Kotlin

android {
    ...
    buildTypes {
        getByName("release") {
            isShrinkResources = true
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

Groovy

android {
    ...
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android.txt'),
                'proguard-rules.pro'
        }
    }
}

Si vous n'avez pas encore compilé votre application à l'aide de minifyEnabled pour la minification de code, essayez ceci avant d'activer shrinkResources, car vous devrez peut-être modifier votre fichier proguard-rules.pro pour conserver les classes ou les méthodes créées ou appelées de façon dynamique avant de commencer à supprimer des ressources.

Personnaliser les ressources à conserver

Si vous souhaitez conserver ou supprimer des ressources spécifiques, créez un fichier XML dans votre projet avec une balise <resources>, et spécifiez chaque ressource à conserver dans l'attribut tools:keep et chaque ressource à supprimer dans l'attribut tools:discard. Les deux attributs acceptent une liste de noms de ressources séparés par une virgule. Vous pouvez utiliser l'astérisque comme caractère générique.

Par exemple :

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/unused2" />

Enregistrez ce fichier dans les ressources de votre projet, par exemple sous res/raw/keep.xml. Ce fichier n'est pas empaqueté dans votre application lors de la compilation.

Spécifier les ressources à supprimer peut sembler stupide, alors que vous pourriez les supprimer, mais cela peut s'avérer utile lorsque vous utilisez des variantes de compilation. Par exemple, vous pouvez placer toutes vos ressources dans le répertoire de projet commun, puis créer un fichier keep.xml différent pour chaque variante de compilation lorsque vous savez qu'une ressource donnée semble être utilisée dans le code (et n'est donc pas supprimée par le réducteur), mais qu'en fait elle ne sera pas utilisée pour la variante de compilation indiquée. Il est également possible que les outils de compilation aient identifié, à tort, une ressource comme étant nécessaire. Cela peut arriver, car le compilateur ajoute les ID de ressource de façon intégrée et, de ce fait, il se peut que l'analyseur de ressources ne fasse pas la différence entre une ressource véritablement référencée et une valeur entière dans le code ayant la même valeur.

Activer des contrôles de référence stricts

Normalement, le réducteur de ressources peut déterminer avec précision si une ressource est utilisée. Toutefois, si votre code appelle Resources.getIdentifier() (ou si l'une de vos bibliothèques effectue ce type d'appel, comme la bibliothèque AppCompat), cela signifie que votre code recherche des noms de ressources en fonction de chaînes générées dynamiquement. Lorsque vous procédez de la sorte, le réducteur de ressources adopte, par défaut, un comportement défensif, et marque toutes les ressources dont le format de nom correspond comme étant potentiellement utilisées et indisponibles pour la suppression.

Par exemple, avec le code suivant, toutes les ressources avec le préfixe img_ sont marquées comme étant utilisées.

Kotlin

val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)

Java

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

Le réducteur de ressources parcourt également toutes les constantes de chaîne de votre code, ainsi que diverses ressources res/raw/, à la recherche d'URL de ressources dans un format semblable à file:///android_res/drawable//ic_plus_anim_016.png. S'il trouve des chaînes de ce type ou d'autres qui semblent pouvoir être utilisées pour construire des URL semblables, il ne les supprime pas.

Voici quelques exemples du mode de réduction sécurisé activé par défaut. Vous pouvez toutefois désactiver ce comportement "Mieux vaut prévenir que guérir" et spécifier que le réducteur de ressources ne conserve que les ressources dont l'utilisation est certaine. Pour ce faire, définissez shrinkMode sur strict dans le fichier keep.xml, comme suit :

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:shrinkMode="strict" />

Si vous activez le mode de réduction stricte et que votre code fait également référence à des ressources avec des chaînes générées dynamiquement, comme indiqué ci-dessus, vous devez conserver manuellement ces ressources à l'aide de l'attribut tools:keep.

Supprimer les autres ressources non utilisées

Le réducteur de ressources Gradle ne supprime que les ressources qui ne sont pas référencées par votre code d'application. Cela signifie qu'il ne supprime pas les autres ressources correspondant à des configurations d'appareil différentes. Si nécessaire, vous pouvez utiliser la propriété resConfigs du plug-in Android Gradle pour supprimer les fichiers d'autres ressources dont votre application n'a pas besoin.

Par exemple, si vous utilisez une bibliothèque qui contient des ressources linguistiques (comme AppCompat ou les services Google Play), votre application inclut toutes les chaînes traduites pour les messages de ces bibliothèques, et ce, que le reste de votre application soit traduite ou non dans les langues de ces ressources. Si vous souhaitez conserver uniquement les langues prises en charge officiellement par votre application, vous pouvez les spécifier à l'aide de la propriété resConfig. Toutes les ressources concernant les langues non spécifiées sont supprimées.

L'extrait suivant montre comment limiter vos ressources linguistiques à l'anglais et au français :

Kotlin

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

Groovy

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}

Lorsque vous publiez une application au format Android App Bundle, seules les langues configurées sur l'appareil de l'utilisateur sont téléchargées par défaut lors de l'installation de l'application. De même, seules les ressources correspondant à la densité d'écran de l'appareil et les bibliothèques natives correspondant à l'ABI de l'appareil sont incluses dans le téléchargement. Pour en savoir plus, consultez la configuration d'Android App Bundle.

Pour les anciennes applications publiées avec des APK (créés avant août 2021), vous pouvez personnaliser la densité d'écran ou les ressources ABI à inclure dans votre APK en compilant plusieurs APK ciblant chacun une configuration d'appareil différente.

Fusionner les ressources en double

Par défaut, Gradle fusionne également les ressources portant un nom identique, telles que les drawables du même nom qui peuvent se trouver dans des dossiers de ressources différents. Ce comportement n'est pas contrôlé par la propriété shrinkResources et ne peut pas être désactivé, car il est nécessaire pour éviter les erreurs lorsque plusieurs ressources correspondent au nom recherché par votre code.

La fusion de ressources ne se produit que lorsque plusieurs fichiers partagent les mêmes nom de ressource, type et qualificatif. Gradle sélectionne le fichier qu'il considère comme le meilleur candidat parmi les doublons (en fonction d'un ordre de priorité décrit ci-dessous) et transmet uniquement cette ressource à AAPT en vue d'une distribution dans l'artefact final.

Gradle recherche les ressources en double aux emplacements suivants :

  • Ressources principales, associées à l'ensemble de sources principal, généralement situées dans src/main/res/.
  • Superpositions de variantes, à partir du type et des versions de compilation.
  • Dépendances du projet de bibliothèque.

Gradle fusionne les ressources en double dans l'ordre de priorité en cascade suivant :

Dépendances → Principales → Version de compilation → Type de compilation

Par exemple, si une ressource en double apparaît à la fois dans vos ressources principales et dans une version de compilation, Gradle sélectionne celle qui se trouve dans la version de compilation.

Si des ressources identiques apparaissent dans le même ensemble de sources, Gradle ne peut pas les fusionner et génère une erreur de fusion des ressources. Cela peut se produire si vous définissez plusieurs ensembles de sources dans la propriété sourceSet de votre fichier build.gradle.kts ; par exemple si src/main/res/ et src/main/res2/ contiennent des ressources identiques.

Obscurcir votre code

L'obscurcissement vise à réduire la taille de votre application en raccourcissant les noms des classes, des méthodes et des champs de votre application. Voici un exemple d'obscurcissement à l'aide de R8 :

androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
    android.content.Context mContext -> a
    int mListItemLayout -> O
    int mViewSpacingRight -> l
    android.widget.Button mButtonNeutral -> w
    int mMultiChoiceItemLayout -> M
    boolean mShowTitle -> P
    int mViewSpacingLeft -> j
    int mButtonPanelSideLayout -> K

Bien que l'obscurcissement ne supprime pas le code de votre application, des réductions de taille importantes peuvent être observées dans les applications avec fichiers DEX qui indexent un grand nombre de classes, de méthodes et de champs. Toutefois, comme l'obscurcissement renomme différentes parties du code, des outils supplémentaires sont nécessaires pour certaines tâches, comme l'inspection des traces de la pile. Pour comprendre votre trace de pile après l'obscurcissement, consultez la section qui explique comment décoder une trace de pile obscurcie.

De plus, si votre code repose sur l'affectation de noms prévisibles aux méthodes et classes de votre application (comme lorsque vous utilisez la réflexion), vous devez considérer ces signatures comme des points d'entrée et leur spécifier des règles de conservation, comme indiqué dans la section sur la personnalisation du code à conserver. Ces règles de conservation indiquent non seulement à R8 de conserver ce code dans le fichier DEX final de votre application, mais aussi de conserver son nom d'origine.

Décoder une trace de pile obscurcie

Une fois que R8 a obscurci votre code, il est difficile (voire impossible) de comprendre une trace de pile, car les noms des classes et des méthodes ont peut-être été modifiés. Pour obtenir la trace de pile d'origine, vous devez la retracer.

Optimisation du code

Pour réduire davantage encore la taille de votre application, R8 examine votre code plus en profondeur afin de supprimer plus de code inutilisé ou, si possible, de réécrire votre code pour le rendre moins détaillé. Voici quelques exemples de ces optimisations :

  • Si votre code n'utilise jamais la branche else {} pour une instruction if/else donnée, R8 peut le supprimer de la branche else {}.
  • Si votre code appelle une méthode à un seul endroit, R8 peut la supprimer et l'intégrer au niveau du site d'appel unique.
  • Si R8 détermine qu'une classe ne comporte qu'une seule sous-classe et que la classe proprement dite n'est pas instanciée (par exemple, une classe de base abstraite utilisée uniquement par une classe d'implémentation concrète), R8 peut combiner les deux classes et en supprimer une de l'application.
  • Pour en savoir plus, consultez les articles de blog sur l'optimisation R8 (en anglais) publiés par Jake Wharton.

R8 ne vous permet pas d'activer ou de désactiver des optimisations discrètes, ni de modifier le comportement d'une optimisation. En fait, R8 ignore toutes les règles ProGuard qui tentent de modifier les optimisations par défaut, telles que -optimizations et - optimizationpasses. Il s'agit d'une restriction importante, car à mesure que R8 continue de s'améliorer, maintenir un comportement standard pour les optimisations permet à l'équipe Android Studio de diagnostiquer et de résoudre facilement les problèmes que vous pouvez rencontrer.

Notez que l'activation de l'optimisation modifie les traces de pile de votre application. L'intégration (ou inlining), par exemple, supprime les blocs de pile. Pour savoir comment obtenir les traces de pile d'origine, consultez la section sur le retraçage.

Activer des optimisations plus agressives

R8 inclut un ensemble d'optimisations supplémentaires (appelées "mode complet"), qui se comportent différemment de ProGuard. Ces optimisations sont activées par défaut depuis la version 8.0.0 du plug-in Android Gradle.

Vous pouvez désactiver ces optimisations supplémentaires en incluant le code suivant dans le fichier gradle.properties de votre projet:

android.enableR8.fullMode=false

Étant donné que ces optimisations supplémentaires font que R8 se comporte différemment de ProGuard, elles peuvent vous obliger à inclure des règles ProGuard supplémentaires pour éviter les problèmes d'exécution si vous utilisez des règles conçues pour ProGuard. Par exemple, supposons que votre code fait référence à une classe via l'API Java Reflection. Lorsqu'il n'utilise pas le "mode complet", R8 suppose que vous avez l'intention d'examiner et de manipuler les objets de cette classe au moment de l'exécution, même si votre code ne l'utilise pas, et conserve automatiquement la classe et son initialiseur statique.

Toutefois, lorsque vous utilisez le "mode complet", R8 ne fait pas cette hypothèse et, s'il affirme que votre code n'utilise jamais la classe au moment de l'exécution, il la supprime du fichier DEX final de votre application. Autrement dit, si vous souhaitez conserver la classe et son initialiseur statique, vous devez, pour ce faire, inclure une règle de conservation dans votre fichier de règles.

Si vous rencontrez des problèmes lors de l'utilisation du "mode complet" de R8, consultez la page des questions fréquentes de R8 pour trouver une solution. Si vous ne parvenez pas à résoudre le problème, veuillez signaler un bug.

Retracer les traces de la pile

Le code traité par R8 subit différentes modifications, ce qui peut rendre les traces de la pile plus difficiles à comprendre, étant donné qu'elles ne correspondent plus exactement au code source. Cela peut être le cas des modifications apportées aux numéros de ligne lorsque les informations de débogage ne sont pas conservées. Cela peut être dû à des optimisations telles que l'inlining et l'outlining. C'est avec l'obscurcissement, où même lorsque les classes et les méthodes changent de nom, que les modifications sont les plus importantes.

Pour récupérer la trace de pile d'origine, R8 propose l'outil de ligne de commande retrace, qui fait partie du package d'outils de ligne de commande.

Pour qu'il soit possible de retracer les traces de pile de votre application, vous devez vous assurer que le build conserve suffisamment d'informations à retracer en ajoutant les règles suivantes au fichier proguard-rules.pro de votre module :

-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile

L'attribut LineNumberTable conserve les informations de position dans des méthodes, de sorte que ces positions soient imprimées dans des traces de pile. L'attribut SourceFile garantit que tous les environnements d'exécution potentiels impriment effectivement les informations de position. La directive -renamesourcefileattribute définit simplement le nom du fichier source dans les traces de pile sur SourceFile. Le nom du fichier source d'origine n'est pas requis lors du retraçage, car ce fichier est inclus dans le fichier de mappage.

R8 crée, lors de chaque exécution, un fichier mapping.txt qui contient les informations nécessaires pour remapper les traces de pile sur celles d'origine. Android Studio enregistre le fichier dans le répertoire <module-name>/build/outputs/mapping/<build-type>/.

Lorsque vous publiez votre application sur Google Play, vous pouvez importer le fichier mapping.txt pour chaque version. Si vous la publiez à l'aide de packages Android App Bundle, ce fichier est inclus automatiquement dans le contenu de l'app bundle. Google Play retracera alors les traces de pile entrantes à partir des problèmes signalés par les utilisateurs afin que vous puissiez les examiner dans la Play Console. Pour en savoir plus, consultez l'article du centre d'aide qui explique comment désobscurcir les traces de la pile de plantage.

Résoudre les problèmes dans R8

Cette section décrit certaines stratégies permettant de résoudre des problèmes liés à l'activation de la minification, de l'obscurcissement et de l'optimisation à l'aide de R8. Si vous ne trouvez pas la solution à votre problème dans cette section, consultez également la page des questions fréquentes sur R8 et le guide de dépannage de ProGuard.

Générer un rapport sur le code supprimé (ou conservé)

Pour vous aider à résoudre certains problèmes affectant R8, il peut être utile de consulter un rapport contenant tout le code que R8 a supprimé de votre application. Pour chaque module pour lequel vous souhaitez générer ce rapport, ajoutez -printusage <output-dir>/usage.txt à votre fichier de règles personnalisé. Lorsque vous activez R8 et compilez l'application, R8 génère un rapport contenant le chemin d'accès et le nom de fichier que vous avez spécifiés. Le rapport sur le code supprimé se présente comme suit :

androidx.drawerlayout.R$attr
androidx.vectordrawable.R
androidx.appcompat.app.AppCompatDelegateImpl
    public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
    public boolean hasWindowFeature(int)
    public void setHandleNativeActionModesEnabled(boolean)
    android.view.ViewGroup getSubDecor()
    public void setLocalNightMode(int)
    final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
    public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
    private static final boolean DEBUG
    private static final java.lang.String KEY_LOCAL_NIGHT_MODE
    static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
...

Si vous souhaitez afficher un rapport sur les points d'entrée déterminés par R8 à partir des règles de conservation de votre projet, insérez -printseeds <output-dir>/seeds.txt dans votre fichier de règles personnalisé. Lorsque vous activez R8 et compilez l'application, R8 génère un rapport contenant le chemin d'accès et le nom de fichier que vous avez spécifiés. Le rapport sur les points d'entrée conservés se présente comme suit :

com.example.myapplication.MainActivity
androidx.appcompat.R$layout: int abc_action_menu_item_layout
androidx.appcompat.R$attr: int activityChooserViewStyle
androidx.appcompat.R$styleable: int MenuItem_android_id
androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
androidx.lifecycle.FullLifecycleObserverAdapter
...

Résoudre les problèmes de réduction de ressources

Lorsque vous réduisez des ressources, la fenêtre Compiler  affiche un récapitulatif des ressources supprimées de l'application. Vous devez d'abord cliquer sur Changer d'affichage  sur le côté gauche de la fenêtre pour afficher la sortie texte détaillée à partir de Gradle. Par exemple :

:android:shrinkDebugResources
Removed unused resources: Binary resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning

Gradle crée également un fichier de diagnostic nommé resources.txt dans <module-name>/build/outputs/mapping/release/ (il s'agit du même dossier que celui des fichiers de sortie de ProGuard). Les informations contenues dans ce fichier permettent de savoir quelles ressources en référencent d'autres, et lesquelles sont utilisées ou supprimées.

Par exemple, pour savoir pourquoi @drawable/ic_plus_anim_016 se trouve toujours dans votre application, ouvrez le fichier resources.txt et recherchez ce nom de fichier. Vous apprendrez peut-être qu'il est référencé à partir d'une autre ressource, comme suit :

16:25:48.005 [QUIET] [system.out] &#64;drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out]     &#64;drawable/ic_plus_anim_016

Vous devez maintenant comprendre pourquoi @drawable/add_schedule_fab_icon_anim est accessible. Si vous effectuez une recherche vers le haut, vous verrez que cette ressource est répertoriée sous "The root reachable resources are:" (Les ressources accessibles à la racine sont les suivantes :). Cela signifie qu'il existe une référence à add_schedule_fab_icon_anim (en d'autres termes, son ID R.drawable a été trouvé dans le code accessible).

Si vous n'utilisez pas de vérification stricte, un ID de ressource peut être marqué comme accessible s'il existe des constantes de chaîne susceptibles d'être utilisées pour créer des noms pour des ressources chargées dynamiquement. Dans ce cas, si vous recherchez le nom de la ressource dans le résultat de la compilation, un message semblable à celui-ci peut s'afficher :

10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
    used because it format-string matches string pool constant ic_plus_anim_%1$d.

Si vous voyez l'une de ces chaînes et que vous êtes sûr qu'elle n'est pas utilisée pour charger dynamiquement la ressource donnée, vous pouvez utiliser l'attribut tools:discard pour demander au système de compilation de la supprimer, comme indiqué dans la section sur la personnalisation des ressources à conserver.