Optimisation pour les auteurs de bibliothèques

En tant qu'auteur de bibliothèque, vous devez vous assurer que les développeurs d'applications peuvent facilement intégrer votre bibliothèque à leur application tout en offrant une expérience utilisateur finale de haute qualité. Cela signifie que votre bibliothèque doit être compatible avec l'optimisation Android (R8) sans nécessiter de configuration supplémentaire de la part du développeur. Vous pouvez également indiquer que la bibliothèque n'est pas adaptée à une utilisation sur Android. Il est essentiel que les bibliothèques destinées à être utilisées sur Android n'empêchent pas les optimisations importantes des applications et qu'elles respectent les exigences d'optimisation supplémentaires.

Cette documentation s'adresse aux développeurs de bibliothèques publiées, mais elle peut également être utile aux développeurs de modules de bibliothèque internes dans une application volumineuse et modularisée.

Si vous êtes développeur d'applications et que vous souhaitez en savoir plus sur l'optimisation de votre application Android, consultez Activer l'optimisation des applications. Pour savoir quelles bibliothèques appropriées utiliser, consultez Choisir judicieusement les bibliothèques.

Comprendre les types de règles de conservation

Il existe deux types distincts de règles de conservation que vous pouvez utiliser dans les bibliothèques :

  • Les règles de conservation des consommateurs doivent spécifier des règles qui conservent tout ce que la bibliothèque reflète. Si une bibliothèque utilise la réflexion ou JNI pour appeler son code, ou le code défini par une application cliente, ces règles doivent décrire le code à conserver. Les bibliothèques doivent empaqueter les règles de conservation des consommateurs, qui utilisent le même format que les règles de conservation des applications. Ces règles sont regroupées dans des artefacts de bibliothèque (AAR ou JAR) et sont utilisées automatiquement lors de l'optimisation des applications Android lorsque la bibliothèque est utilisée. Ces règles sont conservées dans le fichier spécifié avec la consumerProguardFiles propriété dans votre build.gradle.kts (ou build.gradle) fichier. Pour en savoir plus, consultez Écrire des règles de conservation des consommateurs.
  • Les règles de conservation de la compilation de la bibliothèque sont appliquées lors de la compilation de votre bibliothèque. Elles ne sont nécessaires que si vous décidez d'optimiser partiellement votre bibliothèque au moment de la compilation. Elles doivent empêcher la suppression de l'API publique de la bibliothèque. Sinon, l'API publique ne sera pas présente dans la distribution de la bibliothèque, ce qui signifie que les développeurs d'applications ne pourront pas l'utiliser. Ces règles sont conservées dans le fichier spécifié avec la propriété proguardFiles dans votre build.gradle.kts (ou build.gradle) fichier. Pour en savoir plus, consultez Optimiser la compilation de la bibliothèque AAR.

Exigences et consignes d'optimisation

La configuration R8 dans les bibliothèques a un impact global sur la taille et les performances binaires finales de l'application consommatrice. Outre les bonnes pratiques générales concernant les règles de conservation , les auteurs de bibliothèques doivent respecter des exigences spécifiques et tenir compte de consignes supplémentaires.

Respecter les exigences d'optimisation

L'inefficacité des bibliothèques contribue fortement à l'encombrement des applications, au gaspillage de mémoire, aux démarrages lents et aux erreurs L'application ne répond pas (ANR). Les bibliothèques doivent éviter de ne pas respecter les exigences suivantes afin de ne pas réduire considérablement la qualité des applications et l'expérience utilisateur.

  • Pas de règles de conservation larges ou à l'échelle du package : votre bibliothèque ne doit pas inclure de règles de conservation larges qui conservent la majeure partie du code de votre bibliothèque ou d'une autre bibliothèque. Les règles de conservation larges peuvent résoudre les plantages à court terme, mais elles augmentent la taille de l'application de toutes les applications qui utilisent votre bibliothèque.

    N'incluez pas de règles de conservation à l'échelle du package (telles que -keep class com.mylibrary.** {*; }) pour les packages de votre bibliothèque ou d'autres bibliothèques référencées. Ces règles limitent l'optimisation de ces packages dans toutes les applications qui utilisent votre bibliothèque.

  • Pas de règles globales inappropriées : n'utilisez jamais d'options globales telles que -dontobfuscate ou -allowaccessmodification.

  • Utiliser codegen plutôt que la réflexion dans la mesure du possible : dans la mesure du possible, utilisez la génération de code (codegen) plutôt que la réflexion. Codegen et la réflexion sont deux approches courantes pour éviter le code récurrent lors de la programmation, mais codegen est plus compatible avec un optimiseur d'application tel que R8.

    Avec codegen, le code est analysé et modifié pendant le processus de compilation. Comme il n'y a pas de modifications majeures après la compilation, l'optimiseur sait quel code est finalement nécessaire et ce qui peut être supprimé en toute sécurité.

    Avec la réflexion, le code est analysé et manipulé au moment de l'exécution. Comme le code n'est pas réellement finalisé tant qu'il n'est pas exécuté, l'optimiseur ne sait pas quel code peut être supprimé en toute sécurité. Il supprimera probablement le code utilisé de manière dynamique par réflexion lors de l'exécution, ce qui entraînera des plantages d'application pour les utilisateurs.

    De nombreuses bibliothèques modernes utilisent codegen au lieu de la réflexion. Consultez KSP pour obtenir un point d'entrée commun, utilisé par Room, Dagger2 et bien d'autres.

  • Compatibilité avec le mode complet R8 : votre bibliothèque ne doit pas planter lorsque le mode complet R8 est activé. Le mode complet de R8 est le mode recommandé pour utiliser R8. Il est défini par défaut depuis AGP 8.0, qui a été stabilisé en 2023. Si votre bibliothèque plante sous R8, la solution consiste à identifier le point d'entrée spécifique de la réflexion ou de JNI et à ajouter une règle ciblée, et non à conserver l'ensemble du package.

Autres recommandations

Outre les exigences d'optimisation, voici d'autres recommandations.

  • N'utilisez pas -repackageclasses dans le fichier de règles de conservation des consommateurs de votre bibliothèque. Toutefois, pour optimiser la compilation de votre bibliothèque, vous pouvez utiliser -repackageclasses avec un nom de package interne, tel que <your.library.package>.internal, dans le fichier de règles de conservation de la compilation de votre bibliothèque. Cela peut améliorer l'efficacité de votre bibliothèque dans les applications non optimisées. Toutefois, cela n'est généralement pas nécessaire, car les applications doivent également être optimisées.
  • Déclarez tous les attributs dont vous avez besoin pour que votre bibliothèque fonctionne dans les fichiers de règles de conservation de votre bibliothèque, même s'il peut y avoir un chevauchement avec les attributs définis dans proguard-android-optimize.txt.
  • Si vous avez besoin des attributs suivants dans la distribution de votre bibliothèque, conservez-les dans le fichier de règles de conservation de la compilation de votre bibliothèque, et non dans le fichier de règles de conservation des consommateurs de votre bibliothèque :
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • Les auteurs de bibliothèques doivent conserver l'attribut RuntimeVisibleAnnotations dans leurs règles de conservation des consommateurs si des annotations sont utilisées au moment de l'exécution.
  • Les auteurs de bibliothèques ne doivent pas utiliser les options globales suivantes dans leurs règles de conservation des consommateurs :
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

Quand la réflexion est acceptable

Si vous devez utiliser la réflexion, vous ne devez refléter que l'un des éléments suivants :

  • Types ciblés spécifiques (implémenteurs d'interface ou sous-classes spécifiques)
  • Code utilisant une annotation d'exécution spécifique

L'utilisation de la réflexion de cette manière limite le coût d'exécution et permet d'écrire des règles de conservation des consommateurs ciblées.

Cette forme de réflexion spécifique et ciblée est un modèle que vous pouvez voir à la fois dans le framework Android (par exemple, lors de l'inflation d'activités, de vues et de drawables) et dans les bibliothèques AndroidX (par exemple, lors de la construction de WorkManager ListenableWorkers ou de RoomDatabases). En revanche, la réflexion ouverte de Gson n'est pas appropriée pour une utilisation dans les applications Android.

Idées reçues

Quelques idées reçues peuvent vous amener à configurer R8 de manière incorrecte. En voici quelques exemples :

Configurer l'empaquetage des règles

Pour vous assurer que vos règles de conservation des consommateurs sont appliquées correctement, vous devez les empaqueter de manière appropriée en fonction du format de votre bibliothèque.

Bibliothèques AAR

Pour ajouter des règles de consommateur à une bibliothèque AAR, utilisez l'option consumerProguardFiles dans le script de compilation du module de bibliothèque Android. Pour en savoir plus, consultez nos conseils sur la création de modules de bibliothèque.

Kotlin

android {
    defaultConfig {
        consumerProguardFiles("consumer-proguard-rules.pro")
    }
    ...
}

Groovy

android {
    defaultConfig {
        consumerProguardFiles 'consumer-proguard-rules.pro'
    }
    ...
}

Bibliothèques JAR

Pour regrouper des règles avec votre bibliothèque Kotlin ou Java fournie sous forme de JAR, placez votre fichier de règles dans le répertoire META-INF/proguard/ du JAR final, avec n'importe quel nom de fichier. Par exemple, si votre code se trouve dans <libraryroot>/src/main/kotlin, placez un fichier de règles de consommateur à <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro et les règles seront regroupées au bon emplacement dans votre JAR de sortie.

Vérifiez que le JAR final regroupe correctement les règles en vous assurant qu'elles se trouvent dans le répertoire META-INF/proguard.

Optimiser la compilation de la bibliothèque AAR (avancé)

En règle générale, vous n'avez pas besoin d'optimiser directement une compilation de bibliothèque, car les optimisations possibles au moment de la compilation de la bibliothèque sont très limitées. En tant que développeur de bibliothèque, vous devez réfléchir à plusieurs étapes d'optimisation et conserver le comportement, à la fois au moment de la compilation de la bibliothèque et de l'application, avant d'optimiser cette bibliothèque.

Si vous souhaitez toujours optimiser votre bibliothèque au moment de la compilation, le plug-in Android Gradle est compatible.

Kotlin

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        configureEach {
            consumerProguardFiles("consumer-rules.pro")
        }
    }
}

Groovy

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                'proguard-rules.pro'
        }
        configureEach {
            consumerProguardFiles "consumer-rules.pro"
        }
    }
}

Notez que le comportement de proguardFiles est très différent de celui de consumerProguardFiles :

  • proguardFiles sont utilisés au moment de la compilation, souvent avec getDefaultProguardFile("proguard-android-optimize.txt"), pour définir la partie de votre bibliothèque à conserver lors de la compilation de la bibliothèque. Il s'agit au minimum de votre API publique.
  • consumerProguardFiles est en revanche empaqueté dans la bibliothèque pour affecter les optimisations ultérieures, lors de la compilation d'une application qui utilise votre bibliothèque.

Par exemple, si votre bibliothèque utilise la réflexion pour construire des classes internes, vous devrez peut-être définir les règles de conservation dans proguardFiles et consumerProguardFiles.

Si vous utilisez -repackageclasses dans la compilation de votre bibliothèque, reconditionnez les classes dans un sous-package à l'intérieur du package de votre bibliothèque. Par exemple, utilisez -repackageclasses 'com.example.mylibrary.internal' au lieu de -repackageclasses 'internal'.

Compatibilité avec différentes versions de R8 (avancé)

Vous pouvez adapter les règles pour cibler des versions spécifiques de R8. Cela permet à votre bibliothèque de fonctionner de manière optimale dans les projets qui utilisent des versions plus récentes de R8, tout en permettant aux règles existantes de continuer à être utilisées dans les projets avec des versions plus anciennes de R8.

Pour spécifier des règles R8 ciblées, vous devez les inclure dans le répertoire META-INF/com.android.tools à l'intérieur de classes.jar d'un AAR ou dans le répertoire META-INF/com.android.tools d'un JAR.

In an AAR library:
    proguard.txt (legacy location, the file name must be "proguard.txt")
    classes.jar
    └── META-INF
        └── com.android.tools (location of targeted R8 rules)
            ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
            └── ... (more directories with the same name format)

In a JAR library:
    META-INF
    ├── proguard/<ProGuard-rule-files> (legacy location)
    └── com.android.tools (location of targeted R8 rules)
        ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
        └── ... (more directories with the same name format)

Dans le répertoire META-INF/com.android.tools, il peut y avoir plusieurs sous-répertoires dont les noms sont au format r8-from-<X>-upto-<Y> pour indiquer les versions de R8 pour lesquelles les règles sont écrites. Chaque sous-répertoire peut contenir un ou plusieurs fichiers contenant les règles R8, avec n'importe quel nom de fichier et extension.

Notez que les parties -from-<X> et -upto-<Y> sont facultatives, la version <Y> est exclusive et les plages de versions sont généralement continues, mais peuvent également se chevaucher.

Par exemple, r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 et r8-from-8.2.0 sont des noms de répertoire représentant un ensemble de règles R8 ciblées. Les règles du répertoire r8 peuvent être utilisées par toutes les versions de R8. Les règles du répertoire r8-from-8.0.0-upto-8.2.0 peuvent être utilisées par R8 à partir de la version 8.0.0 jusqu'à la version 8.2.0, mais sans l'inclure.

Le plug-in Android Gradle utilise ces informations pour sélectionner toutes les règles qui peuvent être utilisées par la version actuelle de R8. Si une bibliothèque ne spécifie pas de règles R8 ciblées, le plug-in Android Gradle sélectionne les règles des anciens emplacements (proguard.txt pour un AAR ou META-INF/proguard/<ProGuard-rule-files> pour un JAR).