Optimiser la vitesse de compilation

Plus ils sont longs, plus les temps de compilation ralentissent le processus de développement. Cette page propose quelques techniques contribuant à résoudre les goulots d'étranglement liés à ce problème.

Voici les principales étapes à suivre pour améliorer la vitesse de compilation de votre application :

  1. Optimisez votre configuration de compilation en prenant quelques habitudes qui auront un impact positif immédiat sur la plupart des projets Android Studio.
  2. Effectuez le profilage de votre build afin d'identifier et d'analyser certains goulots d'étranglement qui peuvent être propres à votre projet ou à votre poste de travail.

Lorsque vous développez votre application, dans la mesure du possible, vous devez effectuer le déploiement sur un appareil exécutant Android version 7.0 (niveau d'API 24) ou ultérieure. Les versions plus récentes de la plate-forme Android disposent de meilleurs mécanismes de transmission des mises à jour de votre application, tels qu'Android Runtime (ART) et la prise en charge native de plusieurs fichiers DEX.

Remarque : Après votre première compilation propre, vous remarquerez peut-être que les compilations suivantes (qu'elles soient propres ou incrémentielles) sont beaucoup plus rapides (même sans utiliser les optimisations décrites sur cette page). En effet, le daemon Gradle a une période "d'échauffement" qui lui permet ensuite d'améliorer ses performances, comme c'est le cas avec d'autres processus JVM.

Optimiser votre configuration de compilation

Suivez ces conseils pour accélérer la compilation de votre projet Android Studio.

S'assurer que vos outils restent à jour

Les outils Android bénéficient d'optimisations de compilation et de nouvelles fonctionnalités à presque chaque mise à jour. Certains conseils figurant sur cette page supposent que vous utilisez la dernière version. Pour profiter des dernières optimisations, les outils suivants doivent être à jour :

Utiliser KSP au lieu de Kapt

L'outil de traitement des annotations Kotlin (Kapt) est beaucoup plus lent que le processeur de symboles Kotlin (KSP). Si vous écrivez une source Kotlin annotée et que vous utilisez des outils qui traitent les annotations (tels que Room) compatibles avec KSP, vous devez migrer vers KSP.

Éviter de compiler des ressources inutiles

Évitez de compiler et d'inclure des ressources que vous ne testez pas (par exemple, les ressources d'autres langues ou d'autres densités d'écran). À la place, spécifiez une seule langue et une seule densité d'écran pour votre type de développement, comme illustré dans l'exemple suivant :

Groovy

android {
    ...
    productFlavors {
        dev {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resourceConfigurations "en", "xxhdpi"
        }
        ...
    }
}

Kotlin

android {
    ...
    productFlavors {
        create("dev") {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resourceConfigurations("en", "xxhdpi")
        }
        ...
    }
}

Testez l'intégration du portail du plug-in Gradle en dernier

Sous Android, tous les plug-ins se trouvent dans les dépôts google() et mavenCentral(). Cependant, votre build peut avoir recours à des plug-ins tiers résolus à l'aide du service gradlePluginPortal().

Pour les recherches, Gradle ouvre les dépôts dans l'ordre où ils sont déclarés. Par conséquent, les performances de compilation sont améliorées si les dépôts les plus hauts dans la liste contiennent la plupart des plug-ins. Il est donc judicieux d'essayer de mettre l'entrée gradlePluginPortal() en dernier dans le bloc de dépôts de votre fichier settings.gradle. Dans la plupart des cas, cela réduit considérablement le nombre de recherches redondantes de plug-ins et améliore la vitesse de compilation.

Pour en savoir plus sur la façon dont Gradle accède à plusieurs dépôts, consultez la section Déclarer plusieurs dépôts dans la documentation Gradle.

Utiliser des valeurs de configuration de compilation statiques avec votre version de débogage

Utilisez toujours des valeurs statiques pour les propriétés figurant dans le fichier manifeste ou les fichiers de ressources correspondant au type de compilation de débogage.

L'utilisation de codes de version dynamiques, de noms de versions, de ressources ou de toute autre logique de compilation qui modifie le fichier manifeste nécessite une compilation complète de l'application chaque fois que vous souhaitez apporter une modification, même si celle-ci ne nécessite qu'un remplacement rapide. Si votre configuration de compilation nécessite des propriétés dynamiques de ce type, isolez-les dans vos variantes de compilation et conservez les valeurs statiques pour vos versions de débogage, comme illustré dans l'exemple suivant :

  ...
  // Use a filter to apply onVariants() to a subset of the variants.
  onVariants(selector().withBuildType("release")) { variant ->
      // Because an app module can have multiple outputs when using multi-APK, versionCode
      // is only available on the variant output.
      // Gather the output when we are in single mode and there is no multi-APK.
      val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }

      // Create the version code generating task.
      val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
          it.outputFile.set(project.layout.buildDirectory.file("versionCode${variant.name}.txt"))
      }

      // Wire the version code from the task output.
      // map will create a lazy Provider that:
      // 1. Runs just before the consumer(s), ensuring that the producer (VersionCodeTask) has run
      //    and therefore the file is created.
      // 2. Contains task dependency information so that the consumer(s) run after the producer.
      mainOutput.versionCode.set(versionCodeTask.flatMap { it.outputFile.map { it.asFile.readText().toInt() } })
  }
  ...

  abstract class VersionCodeTask : DefaultTask() {

    @get:OutputFile
    abstract val outputFile: RegularFileProperty

    @TaskAction
    fun action() {
        outputFile.get().asFile.writeText("1.1.1")
    }
  }

Consultez la recette setVersionsFromTask sur GitHub pour découvrir comment définir un code de version dynamique dans un projet.

Utiliser des versions de dépendance statiques

Lorsque vous déclarez des dépendances dans vos fichiers build.gradle, évitez d'utiliser des numéros de version dynamiques (ceux qui se terminent par un signe "plus", tels que 'com.android.tools.build:gradle:2.+'). L'utilisation de numéros de version dynamiques peut entraîner des mises à jour de version inattendues, des difficultés pour résoudre les différences de version et des compilations plus lentes, car Gradle recherche des mises à jour. Utilisez plutôt des numéros de version statiques.

Créer des modules de bibliothèque

Dans votre application, recherchez du code que vous pouvez convertir en module de bibliothèque Android. De cette manière, la modularisation de votre code permet au système de compilation de ne s'intéresser qu'aux modules que vous modifiez et de mettre en cache ces résultats pour les compilations futures. Cette technique permet également de gagner en efficacité lorsque vous exécutez plusieurs projets en parallèle (lorsque vous activez cette optimisation).

Créer des tâches pour une logique de compilation personnalisée

Une fois que vous avez créé un profil de compilation, s'il apparaît qu'une partie relativement longue du temps de compilation est consacrée à la phase de **configuration des projets**, examinez vos scripts build.gradle et recherchez du code que vous pouvez inclure dans une tâche Gradle personnalisée. En déplaçant une partie de la logique de compilation dans une tâche, vous vous assurez que celle-ci ne s'exécute que lorsque cela est nécessaire, que les résultats peuvent être mis en cache pour les compilations suivantes et que cette logique peut être exécutée en parallèle si vous activez l'exécution de plusieurs projets en parallèle. Pour en savoir plus sur les tâches pour la logique de compilation personnalisée, consultez la documentation officielle de Gradle.

Astuce : Si votre compilation inclut un grand nombre de tâches personnalisées, vous pouvez désencombrer vos fichiers build.gradle en créant des classes de tâches personnalisées. Ajoutez vos classes au répertoire project-root/buildSrc/src/main/groovy/. Gradle les inclura automatiquement dans le chemin des classes pour tous les fichiers build.gradle de votre projet.

Convertir des images au format WebP

WebP est un format de fichier image qui fournit une compression avec pertes (comme JPEG) ainsi qu'une transparence (comme PNG). WebP peut fournir une meilleure compression que JPEG ou PNG.

En réduisant la taille des fichiers image sans avoir à les compresser lors de la compilation, vous pouvez accélérer le processus, en particulier si votre application utilise beaucoup de ressources de ce type. Cependant, vous constaterez peut-être une légère augmentation de l'utilisation du processeur de l'appareil lors de la décompression des images WebP. Utilisez Android Studio pour convertir facilement vos images au format WebP.

Désactiver le traitement PNG

Si vous ne convertissez pas vos images PNG au format WebP, vous pouvez accélérer votre compilation en désactivant la compression automatique des images chaque fois que vous compilez votre application.

Si vous utilisez le plug-in Android Gradle 3.0.0 ou version ultérieure, le traitement PNG est désactivé par défaut pour le type de version de débogage. Pour désactiver cette optimisation pour d'autres types de compilation, ajoutez le code suivant à votre fichier build.gradle :

Groovy

android {
    buildTypes {
        release {
            // Disables PNG crunching for the "release" build type.
            crunchPngs false
        }
    }
}

Kotlin

android {
    buildTypes {
        getByName("release") {
            // Disables PNG crunching for the "release" build type.
            isCrunchPngs = false
        }
    }
}

Étant donné que les types de compilation ou les types de produit ne définissent pas cette propriété, vous devez la définir manuellement sur true lorsque vous créez la version de votre application.

Tester le récupérateur de mémoire en parallèle de la JVM

Vous pouvez améliorer les performances de compilation en configurant le récupérateur de mémoire JVM optimal utilisé par Gradle. Bien que JDK 8 soit configuré par défaut pour utiliser le récupérateur de mémoire en parallèle, JDK 9 et les versions ultérieures utilisent le récupérateur de mémoire G1.

Pour améliorer les performances de compilation, nous vous recommandons de tester vos compilations Gradle avec le récupérateur de mémoire en parallèle. Dans gradle.properties, définissez les paramètres suivants :

org.gradle.jvmargs=-XX:+UseParallelGC

Si d'autres options sont déjà définies dans ce champ, ajoutez la suivante :

org.gradle.jvmargs=-Xmx1536m -XX:+UseParallelGC

Pour mesurer la vitesse de compilation à l'aide de différentes configurations, consultez la section Effectuer le profilage de votre build.

Augmenter la taille du tas de mémoire de la JVM

En cas de lenteur de la compilation, en particulier lorsque la récupération de mémoire prend plus de 15 % du temps de compilation dans les résultats de Build Analyzer, vous devez augmenter la taille du tas de mémoire de la machine virtuelle Java (JVM) Dans le fichier gradle.properties, définissez la limite sur 4, 6 ou 8 gigaoctets comme indiqué dans l'exemple suivant :

org.gradle.jvmargs=-Xmx6g

Testez ensuite l'amélioration de la vitesse de compilation. Le moyen le plus simple de déterminer la taille optimale du tas de mémoire est d'augmenter légèrement la limite, puis de tester si l'amélioration de la vitesse de compilation est suffisante.

Si vous utilisez également le récupérateur de mémoire en parallèle de la JVM, l'intégralité de la ligne doit se présenter comme suit :

org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g

Pour analyser les erreurs de mémoire de la machine virtuelle Java, activez l'option HeapDumpOnOutOfMemoryError. La JVM générera ainsi une empreinte de la mémoire lorsqu'elle sera à court de mémoire.

Utiliser des classes R non transitives

Utilisez des classes R non transitives afin d'accélérer les compilations pour les applications comportant plusieurs modules. Cela permet d'éviter la duplication des ressources en garantissant que la classe R de chaque module ne contient que des références à ses propres ressources, sans extraire de références de ses dépendances. Par conséquent, les compilations sont plus rapides et vous bénéficiez des avantages qui découlent de l'évitement de la compilation. Il s'agit du comportement par défaut dans le plug-in Android Gradle 8.0.0 ou version ultérieure.

Depuis Android Studio Bumblebee, les classes R non transitives sont activées par défaut pour les nouveaux projets. Pour les projets créés avec des versions antérieures d'Android Studio, mettez-les à jour de manière à utiliser des classes R non transitives. Pour ce faire, accédez à Refactor > Migrate to Non-Transitive R Classes (Refactoriser > Migrer vers des classes R non transitives).

Pour en savoir plus sur les ressources d'application et les classes R, consultez la section Présentation des ressources d'application.

Utiliser des classes R non constantes

Utilisez des champs de classe R non constants dans les applications et les tests pour améliorer l'incrémentalité de la compilation Java et permettre une réduction plus précise des ressources. Les champs de classe R ne sont toujours pas constants pour les bibliothèques, car les ressources sont numérotées lors de l'empaquetage de l'APK pour l'application ou le test qui dépend de cette bibliothèque. Il s'agit du comportement par défaut dans le plug-in Android Gradle 8.0.0 ou version ultérieure.

Désactiver l'indicateur Jetifier

Comme la plupart des projets utilisent directement les bibliothèques AndroidX, vous pouvez supprimer l'indicateur Jetifier pour améliorer les performances de compilation. Pour supprimer l'option Jetifier, définissez android.enableJetifier=false dans votre fichier gradle.properties.

L'outil Build Analyzer peut vérifier si l'indicateur peut être supprimé en toute sécurité pour améliorer les performances de compilation de votre projet et migrer le système pour ne plus avoir recours à des bibliothèques Android Support qui ne font plus l'objet d'une maintenance. Pour en savoir plus sur Build Analyzer, consultez la section Résoudre les problèmes de performances de compilation.

Utiliser le cache de configuration

Le cache de configuration permet à Gradle d'enregistrer des informations sur le graphique des tâches de compilation et de les réutiliser dans les compilations suivantes. Gradle n'a donc pas besoin de reconfigurer l'ensemble de la compilation.

Pour activer le cache de configuration, procédez comme suit :

  1. Vérifiez que tous les plug-ins du projet sont compatibles.

    Utilisez Build Analyzer pour vérifier si votre projet est compatible avec le cache de configuration. Cet outil exécute une séquence de versions de build pour déterminer si la fonctionnalité peut être activée pour le projet. Consultez le problème n° 13490 pour obtenir la liste des plug-ins compatibles.

  2. Ajoutez le code suivant au fichier gradle.properties :

      org.gradle.configuration-cache=true
      # Use this flag carefully, in case some of the plugins are not fully compatible.
      org.gradle.configuration-cache.problems=warn

Lorsque le cache de configuration est activé, la première fois que vous exécutez votre projet, le résultat de la compilation indique Calculating task graph as no configuration cache is available for tasks. Lors des exécutions suivantes, le résultat de la compilation indique Reusing configuration cache.

Pour en savoir plus sur le cache de configuration, consultez l'article de blog sur la configuration de la mise en cache et la documentation Gradle à propos du cache de configuration.

Problèmes de cache de configuration introduits dans Gradle 8.1 et le plug-in Android Gradle 8.1

Le cache de configuration est devenu stable dans Gradle 8.1 et a introduit le suivi des API de fichiers. Les appels tels que File.exists(), File.isDirectory() et File.list() sont enregistrés par Gradle pour suivre les fichiers d'entrée de configuration.

Le plug-in Android Gradle (AGP) 8.1 utilise ces API File pour certains fichiers que Gradle ne doit pas considérer comme des entrées de cache. Cela déclenche une invalidation supplémentaire du cache lorsqu'il est utilisé avec Gradle 8.1 ou version ultérieure, ce qui ralentit les performances de compilation. Les éléments suivants sont traités comme des entrées de cache dans l'AGP 8.1:

Entrée Issue Tracker Corrigé dans
$GRADLE_USER_HOME/android/FakeDependency.jar Problème 289232054 AGP 8.2
sortie cmake Problème 287676077 AGP 8.2
$GRADLE_USER_HOME/.android/analytics.settings Problème 278767328 AGP 8.3

Si vous utilisez ces API ou un plug-in qui les utilise, vous risquez de constater une régression au niveau de la durée de compilation, car une certaine logique de compilation utilisant ces API peut déclencher une invalidation de cache supplémentaire. Pour en savoir plus sur ces modèles et découvrir comment corriger la logique de compilation ou désactiver temporairement le suivi des API de fichiers, consultez la section Améliorations du suivi des entrées de la configuration de compilation.