Les applications Android sont généralement créées à l'aide du système de compilation Gradle. Avant d'examiner en détail la configuration de votre build, nous allons examiner ses concepts afin que vous puissiez examiner le système dans son ensemble.
Qu'est-ce qu'un build ?
Un système de compilation transforme votre code source en une application exécutable. Les compilations impliquent souvent plusieurs outils pour analyser, compiler, lier et empaqueter votre application ou bibliothèque. Gradle utilise une approche basée sur les tâches pour organiser et exécuter ces commandes.
Les tâches encapsulent des commandes qui traduisent leurs entrées en sorties. Les plug-ins définissent les tâches et leur configuration. L'application d'un plug-in à votre compilation enregistre ses tâches et les relie à l'aide de leurs entrées et sorties. Par exemple, l'application du plug-in Android Gradle (AGP) à votre fichier de compilation enregistre toutes les tâches nécessaires à la compilation d'un APK ou d'une bibliothèque Android. Le plug-in java-library
vous permet de créer un fichier JAR à partir du code source Java. Des plug-ins similaires existent pour Kotlin et d'autres langages, mais d'autres plug-ins sont destinés à les étendre. Par exemple, le plug-in protobuf
est destiné à ajouter la prise en charge de protobuf aux plug-ins existants tels que AGP ou java-library
.
Gradle privilégie la convention à la configuration. Par conséquent, les plug-ins fournissent de bonnes valeurs par défaut prêtes à l'emploi, mais vous pouvez configurer davantage la compilation via un langage spécifique au domaine (DSL) déclaratif. Le DSL est conçu pour vous permettre de spécifier ce que vous souhaitez créer, plutôt que comment le créer. La logique des plug-ins gère le "comment". Cette configuration est spécifiée dans plusieurs fichiers de compilation de votre projet (et sous-projets).
Les entrées de tâches peuvent être des fichiers, des répertoires et d'autres informations encodées en tant que types Java (entier, chaînes ou classes personnalisées). Les sorties ne peuvent être que des répertoires ou des fichiers, car ils doivent être écrits sur le disque. Connecter la sortie d'une tâche à l'entrée d'une autre tâche permet de les associer de sorte que l'une doit s'exécuter avant l'autre.
Bien que Gradle prenne en charge l'écriture de code arbitraire et de déclarations de tâches dans vos fichiers de compilation, il peut être plus difficile pour les outils de comprendre votre compilation et de la gérer. Par exemple, vous pouvez écrire des tests pour le code dans les plug-ins, mais pas dans les fichiers de compilation. À la place, vous devez limiter la logique de compilation et les déclarations de tâches aux plug-ins (que vous ou quelqu'un d'autre définissez) et déclarer comment vous souhaitez utiliser cette logique dans vos fichiers de compilation.
Que se passe-t-il lorsqu'un build Gradle s'exécute ?
Les compilations Gradle s'exécutent en trois phases. Chacune de ces phases exécute différentes parties de code que vous définissez dans vos fichiers de compilation.
- L'initialisation détermine les projets et sous-projets inclus dans le build, et configure les chemins d'accès aux classes contenant vos fichiers de compilation et les plug-ins appliqués. Cette phase se concentre sur un fichier de paramètres dans lequel vous déclarez les projets à compiler et les emplacements à partir desquels extraire les plug-ins et les bibliothèques.
- Configuration enregistre les tâches pour chaque projet et exécute le fichier de compilation pour appliquer les spécifications de compilation de l'utilisateur. Il est important de comprendre que votre code de configuration n'aura pas accès aux données ni aux fichiers générés lors de l'exécution.
- L'exécution effectue la compilation réelle de votre application. La sortie de la configuration est un graphe orienté acyclique (DAG) de tâches, représentant toutes les étapes de compilation requises demandées par l'utilisateur (les tâches fournies sur la ligne de commande ou par défaut dans les fichiers de compilation). Ce graphique représente la relation entre les tâches, soit de manière explicite dans la déclaration d'une tâche, soit en fonction de ses entrées et sorties. Si une tâche a une entrée qui est la sortie d'une autre tâche, elle doit s'exécuter après l'autre tâche. Cette phase exécute des tâches obsolètes dans l'ordre défini dans le graphique. Si les entrées d'une tâche n'ont pas changé depuis sa dernière exécution, Gradle l'ignore.
Pour en savoir plus, consultez le cycle de vie des compilations Gradle.
DSL de configuration
Gradle utilise un langage spécifique au domaine (DSL) pour configurer les compilations. Cette approche déclarative se concentre sur la spécification de vos données plutôt que sur l'écriture d'instructions étape par étape (impératives). Vous pouvez écrire vos fichiers de compilation en Kotlin ou Groovy, mais nous vous recommandons vivement d'utiliser Kotlin.
Les DSL tentent de permettre à tous, experts du domaine et programmeurs, de contribuer plus facilement à un projet, en définissant un petit langage qui représente les données de manière plus naturelle. Les plug-ins Gradle peuvent étendre le DSL pour configurer les données dont ils ont besoin pour leurs tâches.
Par exemple, la configuration de la partie Android de votre build peut se présenter comme suit :
Kotlin
android { namespace = "com.example.app" compileSdk = 34 // ... defaultConfig { applicationId = "com.example.app" minSdk = 34 // ... } }
Groovy
android { namespace 'com.example.myapplication' compileSdk 34 // ... defaultConfig { applicationId "com.example.myapplication" minSdk 24 // ... } }
En arrière-plan, le code DSL ressemble à ceci :
fun Project.android(configure: ApplicationExtension.() -> Unit) {
...
}
interface ApplicationExtension {
var compileSdk: Int
var namespace: String?
val defaultConfig: DefaultConfig
fun defaultConfig(configure: DefaultConfig.() -> Unit) {
...
}
}
Chaque bloc du DSL est représenté par une fonction qui utilise un lambda pour le configurer et une propriété portant le même nom pour y accéder. Le code de vos fichiers de compilation ressemble alors davantage à une spécification de données.
Dépendances externes
Le système de compilation Maven a introduit un système de stockage et de gestion de dépendance de spécification. Les bibliothèques sont stockées dans des dépôts (serveurs ou répertoires), avec des métadonnées, y compris leur version et leurs dépendances sur d'autres bibliothèques. Vous spécifiez les dépôts à rechercher et les versions des dépendances que vous souhaitez utiliser. Le système de compilation les télécharge lors de la compilation.
Les artefacts Maven sont identifiés par le nom du groupe (entreprise, développeur, etc.), le nom de l'artefact (nom de la bibliothèque) et la version de cet artefact. Cette valeur est généralement représentée par group:artifact:version
.
Cette approche améliore considérablement la gestion des builds. Vous entendrez souvent ce que l'on appelle des "dépôts Maven", mais tout dépend de la façon dont les artefacts sont empaquetés et publiés. Ces dépôts et métadonnées ont été réutilisés dans plusieurs systèmes de compilation, y compris Gradle (et Gradle peut publier dans ces dépôts). Les dépôts publics permettent un partage par tous, et les dépôts d'entreprise conservent les dépendances internes en interne.
Vous pouvez également modulariser votre projet en sous-projets (également appelés "modules" dans Android Studio), qui peuvent également être utilisés en tant que dépendances. Chaque sous-projet produit des sorties (telles que des fichiers JAR) qui peuvent être utilisées par les sous-projets ou votre projet de premier niveau. Cela peut améliorer la durée de compilation en isolant les parties à recompiler, ainsi que mieux séparer les responsabilités dans l'application.
Nous verrons plus en détail comment spécifier des dépendances dans la section Ajouter des dépendances de compilation.
Variantes de compilation
Lorsque vous créez une application Android, vous devez généralement créer plusieurs variantes. Les variantes contiennent du code différent ou sont compilées avec des options différentes. Elles sont composées de types de compilation et de types de produit.
Les types de compilation varient les options de compilation déclarées. Par défaut, l'AGP configure des types de compilation "release" et "debug", mais vous pouvez les ajuster et en ajouter d'autres (par exemple, pour la préproduction ou les tests internes).
Une version de débogage ne minimise ni n'obscurcit votre application, ce qui accélère sa compilation et conserve tous les symboles en l'état. Elle marque également l'application comme "débogable", en la signant avec une clé de débogage générique et en autorisant l'accès aux fichiers de l'application installée sur l'appareil. Cela permet d'explorer les données enregistrées dans des fichiers et des bases de données pendant l'exécution de l'application.
Un build optimise l'application, la signe avec votre clé de version et protège les fichiers d'application installés.
Les types de produit vous permettent de modifier la source incluse et les variantes de dépendance de l'application. Par exemple, vous pouvez créer des types de produit "demo" et "full" pour votre application, ou peut-être des types de produit "free" et "paid". Vous écrivez votre source commune dans un répertoire d'ensemble de sources "principal", puis remplacez ou ajoutez la source dans un ensemble de sources nommé d'après le type.
AGP crée des variantes pour chaque combinaison de type de compilation et de type de produit. Si vous ne définissez pas de types, les variantes portent le nom des types de compilation. Si vous définissez les deux, la variante est nommée <flavor><Buildtype>
. Par exemple, avec les types de compilation release
et debug
, et les types demo
et full
, AGP crée des variantes:
demoRelease
demoDebug
fullRelease
fullDebug
Étapes suivantes
Maintenant que vous connaissez les concepts de compilation, examinez la structure de compilation Android de votre projet.