Nouveautés concernant les produits

Configurer et résoudre les problèmes liés aux règles de conservation R8

7 minutes de lecture
Ajesh Pai & Ben Weiss

Dans le développement Android moderne, les utilisateurs s'attendent à ce que les applications soient petites, rapides et sécurisées. L'outil principal du système de compilation Android pour y parvenir est l'optimiseur R8 , le compilateur qui gère la suppression du code mort et des ressources pour la minification, le renommage ou la réduction du code, ainsi que l'optimisation des applications.

L'activation de R8 est une étape essentielle de la préparation d'une application pour sa publication, mais elle nécessite que les développeurs fournissent des conseils sous la forme de "règles de conservation".

Après avoir lu cet article, regardez la vidéo Performance Spotlight Week sur l'activation, le débogage et la résolution des problèmes liés à l'optimiseur R8 sur YouTube.

 

 

Pourquoi les règles de conservation sont-elles nécessaires ?

La nécessité d'écrire des règles de conservation découle d'un conflit fondamental : R8 est un outil d'analyse statique, mais les applications Android s'appuient souvent sur des modèles d'exécution dynamiques tels que la réflexion ou les appels dans et hors du code natif à l'aide de JNI (Java Native Interface).

R8 crée un graphique du code utilisé en analysant les appels directs. Lorsque le code est accessible de manière dynamique, l'analyse statique de R8 ne peut pas le prédire. Il identifie alors ce code comme inutilisé et le supprime, ce qui entraîne des plantages au moment de l'exécution.

Une règle de conservation est une instruction explicite au compilateur R8, qui indique : "Cette classe, cette méthode ou ce champ spécifique est un point d'entrée qui sera accessible de manière dynamique au moment de l'exécution. Vous devez le conserver, même si vous ne trouvez pas de référence directe à celui-ci."

Pour en savoir plus sur les règles de conservation, consultez le guide officiel.

Où écrire les règles de conservation ?

Les règles de conservation personnalisées pour une application sont écrites dans un fichier texte. Par convention, ce fichier est nommé proguard-rules.pro et se trouve à la racine du module d'application ou de bibliothèque. Ce fichier est ensuite spécifié dans le type de compilation release du fichier build.gradle.kts de votre module.

release {

    isShrinkResources = true

    isMinifyEnabled = true

    proguardFiles(

        getDefaultProguardFile("proguard-android-optimize.txt"),

        "proguard-rules.pro",

    )

}

Utiliser le fichier par défaut approprié

La méthode getDefaultProguardFile importe un ensemble de règles par défaut fourni par le SDK Android. Si vous utilisez le mauvais fichier, votre application risque de ne pas être optimisée. Veillez à utiliser proguard-android-optimize.txt. Ce fichier fournit les règles de conservation par défaut pour les composants Android standards et active les optimisations de code de R8. Le fichier obsolète proguard-android.txt ne fournit que les règles de conservation, mais n'active pas les optimisations de R8.

progaurd.png

Comme il s'agit d'un problème de performances grave, nous commençons à avertir les développeurs qu'ils utilisent le mauvais fichier, à partir de la fonctionnalité Android Studio Narwhal 3. À partir de la version 9.0 du plug-in Android Gradle, nous ne prendrons plus en charge le fichier obsolète proguard-android.txt. Veillez donc à passer à la version optimisée.

Comment écrire des règles de conservation ?

Une règle de conservation comporte trois parties principales :

  1. Une option comme -keep ou -keepclassmembers
  2. Des modificateurs facultatifs comme allowshrinking
  3. Une spécification de classe qui définit le code à faire correspondre

Pour obtenir la syntaxe complète et des exemples, consultez le guide pour ajouter des règles de conservation.

Antimodèles de règles de conservation

Il est important de connaître les bonnes pratiques, mais aussi les antimodèles. Ces antimodèles résultent souvent de malentendus ou de raccourcis de dépannage et peuvent être catastrophiques pour les performances d'une compilation de production.

Options globales

Ces indicateurs sont des boutons globaux qui ne doivent jamais être utilisés dans une compilation de version. Ils ne servent qu'au débogage temporaire pour isoler un problème.

L'utilisation de -dontotptimize désactive efficacement les optimisations des performances de R8, ce qui ralentit l'application.

Lorsque vous utilisez -dontobfuscate, vous désactivez tous les renommages, et -dontshrink désactive la suppression du code mort. Ces deux règles globales augmentent la taille de l'application.

Évitez d'utiliser ces indicateurs globaux dans un environnement de production dans la mesure du possible pour offrir une expérience utilisateur plus performante.

Règles de conservation trop générales

Le moyen le plus simple d'annuler les avantages de R8 est d'écrire des règles de conservation trop générales. Les règles de conservation comme celle ci-dessous indiquent à l'optimiseur R8 de ne pas réduire, obscurcir ni optimiser aucune classe de ce package ni aucun de ses sous-packages. Cela supprime complètement les avantages de R8 pour l'ensemble du package. Essayez plutôt d'écrire des règles de conservation précises et spécifiques.

-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMS

Opérateur d'inversion (!)

L'opérateur d'inversion (!) semble être un moyen puissant d'exclure un package d'une règle. Mais ce n'est pas aussi simple. Prenons cet exemple :

-keep class !com.example.my_package.** { *; } // USE WITH CAUTION

Vous pensez peut-être que cette règle signifie "ne pas conserver les classes danscom.example.package." Mais elle signifie en fait "conserver chaque classe, méthode et propriété de l'ensemble de l'application qui ne se trouve pas dans com.example.package." Si cela vous surprend, vérifiez s'il existe des négations dans votre configuration R8.

Règles redondantes pour les composants Android

Une autre erreur courante consiste à ajouter manuellement des règles de conservation pour les Activities, les Services ou les BroadcastReceivers de votre application. C'est inutile. Le fichier proguard-android-optimize.txt par défaut inclut déjà les règles pertinentes pour que ces composants Android standards fonctionnent immédiatement.

De nombreuses bibliothèques apportent également leurs propres règles de conservation. Vous ne devriez donc pas avoir à écrire vos propres règles pour celles-ci. En cas de problème avec les règles de conservation d'une bibliothèque que vous utilisez, il est préférable de contacter l'auteur de la bibliothèque pour connaître le problème.

Bonnes pratiques concernant les règles de conservation

Maintenant que vous savez ce qu'il ne faut pas faire, parlons des bonnes pratiques.

Écrire des règles de conservation précises

Les bonnes règles de conservation doivent être aussi précises et spécifiques que possible. Elles ne doivent conserver que ce qui est nécessaire, ce qui permet à R8 d'optimiser tout le reste.

RègleQualité

 

-keep class com.example.** { ; }

 

Faible : conserve un package entier et ses sous-packages

 

-keep class com.example.MyClass { ; }

 

Faible : conserve une classe entière, ce qui est probablement encore trop large
-keepclassmembers class com.example.MyClass {

    private java.lang.String secretMessage;

    public void onNativeEvent(java.lang.String);

}
Élevée : seules les méthodes et propriétés pertinentes d'une classe spécifique sont conservées

Utiliser des ancêtres communs

Au lieu d'écrire des règles de conservation distinctes pour plusieurs modèles de données différents, écrivez une règle qui cible une classe de base ou une interface commune. La règle ci-dessous indique à R8 de conserver tous les membres des classes qui implémentent cette interface et est hautement évolutive.

# Keep all fields of any class that implements SerializableModel

-keepclassmembers class * implements com.example.models.SerializableModel {

    <fields>;

}

Utiliser des annotations pour cibler plusieurs classes

Créez une annotation personnalisée (par exemple, @Serialize) et utilisez-la pour "taguer" les classes dont les champs doivent être conservés. Il s'agit d'un autre modèle propre, déclaratif et hautement évolutif. Vous pouvez également créer des règles de conservation pour les annotations déjà existantes à partir des frameworks que vous utilisez.

# Keep all fields of any class annotated with @Serialize

-keepclassmembers class * {

    @com.example.annotations.Serialize <fields>;

}

Choisir la bonne option de conservation

L'option de conservation est la partie la plus importante de la règle. Si vous choisissez la mauvaise, vous risquez de désactiver inutilement l'optimisation.

Option de conservationFonctionnement
-keepEmpêche la suppression ou le renommage de la classe et des membres mentionnés dans la déclaration .
-keepclassmembersEmpêche la suppression ou le renommage des membres spécifiés, mais autorise la suppression de la classe elle-même, mais uniquement sur les classes qui ne sont pas supprimées autrement.
-keepclasseswithmembersCombinaison : conserve la classe et ses membres, uniquement si tous les membres spécifiés sont présents.

Pour en savoir plus sur l'option de conservation, consultez notre documentation sur les options de conservation.

Autoriser l'optimisation avec des modificateurs

Les modificateurs tels que allowshrinking et allowobfuscation assouplissent une règle -keep générale, ce qui permet à R8 de retrouver sa puissance d'optimisation. Par exemple, si une bibliothèque héritée vous oblige à utiliser -keep sur une classe entière, vous pourrez peut-être récupérer une partie de l'optimisation en autorisant la minification et l'obscurcissement :

# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it.

-keep,allowshrinking,allowobfuscation class com.example.LegacyClass

Ajouter des options globales pour une optimisation supplémentaire

Au-delà des règles de conservation, vous pouvez ajouter des indicateurs globaux à votre fichier de configuration R8 pour encourager une optimisation encore plus poussée.

-repackageclasses est une option puissante qui indique à R8 de déplacer toutes les classes obscurcies dans un seul package. Cela permet de gagner beaucoup d'espace dans le fichier DEX en supprimant les chaînes de noms de packages redondantes.

-allowaccessmodification permet à R8 d'élargir l'accès (par exemple, de private à public) pour permettre une insertion plus agressive. Cette option est désormais activée par défaut lorsque vous utilisez proguard-android-optimize.txt.

Avertissement : Les auteurs de bibliothèques ne doivent jamais ajouter ces indicateurs d'optimisation globale à leurs règles de consommateur, car ils seraient appliqués de force à l'ensemble de l'application.

Pour plus de clarté, dans la version 9.0 du plug-in Android Gradle, nous allons commencer à ignorer complètement les indicateurs d'optimisation globale des bibliothèques. 

Bonnes pratiques pour les bibliothèques

Toutes les applications Android s'appuient sur des bibliothèques d'une manière ou d'une autre. Parlons donc des bonnes pratiques pour les bibliothèques.

Pour les développeurs de bibliothèques

Si votre bibliothèque utilise la réflexion ou JNI, vous êtes responsable de fournir les règles de conservation nécessaires à ses consommateurs. Ces règles sont placées dans un fichier consumer-rules.pro, qui est ensuite automatiquement regroupé dans le fichier AAR de la bibliothèque.

android {

    defaultConfig {

        consumerProguardFiles("consumer-rules.pro")

    }

    ...

}

Pour les consommateurs de bibliothèques

Filtrer les règles de conservation problématiques

Si vous devez utiliser une bibliothèque qui inclut des règles de conservation problématiques, vous pouvez les filtrer dans votre fichier build.gradle.kts à partir d'AGP 9.0. Cela indique à R8 d'ignorer les règles provenant d'une dépendance spécifique.

release {

    optimization.keepRules {

        // Ignore all consumer rules from this specific library

        it.ignoreFrom("com.somelibrary:somelibrary")

    }

}

La meilleure règle de conservation est l'absence de règle de conservation

La stratégie de configuration R8 ultime consiste à supprimer complètement la nécessité d'écrire des règles de conservation. Pour de nombreuses applications, cela peut être obtenu en choisissant des bibliothèques modernes qui privilégient la génération de code à la réflexion. Avec la génération de code, l'optimiseur peut plus facilement déterminer quel code est réellement utilisé au moment de l'exécution et quel code peut être supprimé. De plus, ne pas utiliser de réflexion dynamique signifie qu'il n'y a pas de points d'entrée "masqués". Par conséquent, aucune règle de conservation n'est nécessaire. Lorsque vous choisissez une nouvelle bibliothèque, préférez toujours une solution qui utilise la génération de code plutôt que la réflexion.

Pour en savoir plus sur le choix des bibliothèques, consultez Choisir une bibliothèque judicieusement.

Déboguer et résoudre les problèmes liés à votre configuration R8

Lorsque R8 supprime du code qu'il aurait dû conserver ou que votre APK est plus volumineux que prévu, utilisez ces outils pour diagnostiquer le problème.

Rechercher les règles de conservation en double et globales

Étant donné que R8 fusionne les règles provenant de dizaines de sources, il peut être difficile de savoir quel est l'ensemble de règles "final". L'ajout de cet indicateur à votre fichier proguard-rules.pro génère un rapport complet :

# Outputs the final, merged set of rules to the specified file

-printconfiguration build/outputs/logs/configuration.txt

Vous pouvez effectuer une recherche dans ce fichier pour trouver des règles redondantes ou remonter à une règle problématique (comme -dontoptimize) jusqu'à la bibliothèque spécifique qui l'a incluse.

Demander à R8 : Pourquoi conservez-vous cela ?

Si une classe que vous vous attendiez à voir supprimée se trouve toujours dans votre application, R8 peut vous en indiquer la raison. Il vous suffit d'ajouter cette règle :

# Asks R8 to explain why it's keeping a specific class

class com.example.MyUnusedClass

-whyareyoukeeping 

Lors de la compilation, R8 affiche la chaîne exacte de références qui l'a amené à conserver cette classe, ce qui vous permet de suivre la référence et d'ajuster vos règles.

Pour obtenir un guide complet, consultez la section Résoudre les problèmes liés à R8.

Étapes suivantes

R8 est un outil puissant pour améliorer les performances des applications Android. Son efficacité dépend d'une compréhension correcte de son fonctionnement en tant que moteur d'analyse statique.

En écrivant des règles spécifiques au niveau des membres, en tirant parti des ancêtres et des annotations, et en choisissant soigneusement les bonnes options de conservation, vous pouvez conserver exactement ce qui est nécessaire. La pratique la plus avancée consiste à éliminer complètement la nécessité de règles en choisissant des bibliothèques modernes basées sur la génération de code plutôt que leurs prédécesseurs basés sur la réflexion.

Pendant que vous suivez la semaine Performance Spotlight, n'oubliez pas de regarder la vidéo Spotlight Week d'aujourd'hui sur YouTube et de continuer notre défi R8. Utilisez #optimizationEnabled pour toute question sur l'activation ou la résolution des problèmes liés à R8. Nous sommes là pour vous aider.

Il est temps de constater les avantages par vous-même.

Nous vous mettons au défi d'activer le mode complet R8 pour votre application aujourd'hui.

  1. Pour commencer, suivez nos guides pour les développeurs : Activer l'optimisation des applications.
  2. Vérifiez si vous utilisez toujours proguard-android.txt et remplacez-le par proguard-android-optimize.txt.
  3. Ensuite, mesurez l'impact. Ne vous contentez pas de sentir la différence, vérifiez-la. Mesurez vos gains de performances en adaptant le code de notre exemple d'application Macrobenchmark sur GitHub pour mesurer vos temps de démarrage avant et après.

Nous sommes convaincus que vous constaterez une amélioration significative des performances de votre application.

Pendant que vous y êtes, utilisez le tag social #AskAndroid pour poser vos questions. Tout au long de la semaine, nos experts surveillent vos questions et y répondent.

Rendez-vous demain pour découvrir l'optimisation guidée par profil avec les profils de référence et de démarrage, découvrir comment les performances de rendu Compose se sont améliorées au cours des dernières versions et découvrir les considérations relatives aux performances pour le travail en arrière-plan.

Écrit par :

Lire la suite