Article
Développement mobile
2.12.2015
Perturbation continuum temporel
8 minutes
Temps d'un café
Gradle à la sauce Fabernovel
Roland Borgese
Note : Ce contenu a été créé avant que Fabernovel ne fasse partie du groupe EY, le 5 juillet 2022.

Gradle est un outil de build intégré à Android Studio qui est utilisé afin de gérer et construire les projets Android, en utilisant le langage Groovy.

Bien utilisé, il permet de gérer finement les différentes étapes de la compilation et de simplifier l’intégration continue. Laissez nous vous présenter quelques outils et astuces de Gradle à la sauce Applidium.

La dépendance mère de toutes les dépendances

Pour commencer, voici un outil indispensable pour simplifier votre gestion des dépendances : SDK Manager Plugin. Il télécharge et met à jour automatiquement vos dépendances (API, support library, émulateur). C’est très pratique, notamment avec du code partagé : une fois un projet externe téléchargé, vous n’avez rien à faire de plus. Ouvrez le projet, attendez que les dépendances soient téléchargées ou mises à jour et lancez l’application !

Pour en profiter, ajoutez cette ligne dans le dictionnaire dependencies dans le build.gradle à la racine de votre projet comme ceci :

dependencies { classpath 'com.android.tools.build:gradle:1.3.0' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' //ajoutez moi ici }

Retrouvez le projet et plus d’informations sur github.

Les possibilités offertes par les variantes

productFlavor et buildConfigField

Une autre fonctionnalité très intéressante de Gradle est la possibilité d’utiliser les productFlavors. Pour rappel, vous pouvez définir différentes productFlavors qui permettront de générer différentes versions de votre code.

On peut ainsi vouloir définir plusieurs targets pour différentes filiales d’une entreprise par exemple :

productFlavors { branchOne { applicationId "com.example.branchOne" buildConfigField "String", "CONFIG_ENDPOINT", "http://branchOne.com/android" } branchTwo { applicationId "com.example.branchTwo" buildConfigField "String", "CONFIG_ENDPOINT", "http://branchTwo.org" } }

Vous pouvez définir dans ces variantes des buildConfigFields. Ces champs permettent de regrouper toute votre configuration dans un seul fichier. Ils sont utiles lorsque vous souhaitez accéder facilement à certaines variables de configuration sans être obligé de déclarer différents fichier xml. Typiquement, nous accédons à l’url racine de nos requêtes HTTP via ces champs.

Ils s’appellent ensuite dans le code de cette façon : BuildConfig.NOM_DE_VOTRE_CHAMP.

Une astuce utile : vous pouvez ajouter des dépendances spécifiques pour certaines variantes. Il suffit de préfixer le mot compile par le nom de la variante :

dependencies { compile 'com.android.support:support-v4:22.2.0' branchOneCompile 'com.android.support:appcompat-v7:22.2.0' }

buildType

En parallèle des productFlavors, il existe aussi les buildTypes (debug et release par défaut). Ce sont tous les deux des moyens de générer des variantes pour votre application et ceux-ci sont fusionnés au final pour donner un buildVariant. branchOne et debug donneront la variante branchOneDebug.

La différence réside dans le fait que changer de buildType ne change pas le code de votre application, il est juste traité différemment. Vous accédez à plus de détail côté développeur (optimisation de compilation, niveau de log, etc.) mais le contenu reste le même. Changer de productFlavor au contraire vous permet de changer la configuration de votre application et son contenu.

flavorDimension

Si on veut raffiner et créer des targets selon plusieurs critères, on peut utiliser les flavorDimensions. Celles-ci permettent de générer des variantes suivant plusieurs dimensions. Par exemple le code suivant :

flavorDimensions "brand", "environment" productFlavors { branchOne { flavorDimension "brand" applicationId "com.example.branchOne" buildConfigField "String", "CONFIG_ENDPOINT", "http://branchOne.com/android" } branchTwo { flavorDimension "brand" applicationId "com.example.branchTwo" buildConfigField "String", "CONFIG_ENDPOINT", "http://branchTwo.org" } stubs { flavorDimension "environment" } preprod { flavorDimension "environment" } distrib { flavorDimension "environment" } }

génère les variantes :

En modifiant l’ applicationId comme ci-dessus, vous pouvez ainsi faire coexister les différentes versions de l’application sur votre téléphone.

Un Manifest dynamique

Vous pouvez insérer des placeholders dans votre Manifest grâce à la syntaxe ${name}. Voici un exemple :

<activity android:name=".Main"><intent-filter><action android:name="${applicationId}.foo"></action></intent-filter></activity>

Grâce à ce morceau de code, le placeholder ${applicationId} sera remplacé par la valeur réelle d’ applicationId. Ce qui donnerait pour la variante branchOne :

<action android:name="com.example.branchOne.foo">

Cela peut être utile lors de la souscription au push notamment, où on a besoin de fournir au Manifest un applicationId différent en fonction de la variante.

Dans le cas où vous souhaiteriez créer vos propres placeholders, vous pouvez les définir dans les manifestPlaceholders. La syntaxe est la suivante :

productFlavors { branchOne { manifestPlaceholders = [branchCustoName :"defaultName"] } branchTwo { manifestPlaceholders = [branchCustoName :"otherName"] } }

Pour aller plus loin

Si jamais vous avez besoin de faire des choses encore plus raffinées, vous pouvez exécuter du code supplémentaire dans la tâche applicationVariants.all.

Imaginons que vous vouliez définir un applicationId particulier pour la variante branchTwo et distrib. Vous pouvez faire :

applicationVariants.all { variant -> def mergedFlavor = variant.mergedFlavor switch (variant.flavorName) { case "brancheTwoDistrib": mergedFlavor.setApplicationId("com.example.oldNameBranchTwo") break } }

Parfois des combinaisons buildTypes – flavor n’ont pas de sens et on aimerait pouvoir dire à Gradle de ne pas générer ces variantes-ci. Pas de problème, ceci est possible en utilisant un variant filter. Celui-ci s’utilise de cette façon :

variantFilter { variant -> if (variant.buildType.name.equals('release')) { variant.setIgnore(!variant.getFlavors().get(1).name.equals('distrib')); } if (variant.buildType.name.equals('debug')) { variant.setIgnore(variant.getFlavors().get(1).name.equals('distrib')); } }

Dans l’exemple ci dessus, nous disons à Gradle que les variantes avec un buildType debug ne doivent pas générer la flavor distrib alors que la release ne doit générer que la flavor distrib.

Faciliter l’intégration continue

L’intégration continue est un enjeu important chez Applidium. C’est un moyen de garantir la qualité du code au cours des diverses phases de développement. Voici quelques astuces que nous utilisons pour faciliter sa mise en place.

Avec les buildVariants

Les buildVariants (buildType + productFlavors) que nous avons vues ci-dessus permettent l’existence de plusieurs environnements de développement avec chacun sa configuration propre et ses ressources. En ayant plusieurs environnements de développement, vous pouvez facilement alterner entre des fausses données (stubs, propice pour des tests) ou un environnement proche des conditions réelles d’utilisation (preprod).

Avec un plugin

classpath 'com.novoda:gradle-android-command-plugin:1.4.0'

Ce plugin permet d’exécuter des commandes adb dans les tâches Gradle. Il est en effet important, pour que toute la chaîne de déploiement puisse être invoquée en une seule commande et ainsi minimiser les risques d’erreurs, de concentrer toutes les étapes du déploiement dans un seul outil. Lorsqu’on build+run une application avec Android Studio, celui-ci masque le fait qu’il utilise plusieurs outils : il invoque la tâche assemble de Gradle puis installe et lance l’application via adb. Sur un serveur de CI ou ailleurs où vous n’avez pas accès à Android Studio, vous pouvez ainsi exécuter un build+run complet avec ce plugin.

Ajoutez la ligne dans les dependencies du build.gradle racine, comme pour SDK Manager :

dependencies { classpath 'com.android.tools.build:gradle:1.3.0' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' classpath 'com.novoda:gradle-android-command-plugin:1.4.0' //ajoutez moi ici }

Augmenter le versionCode automatiquement

Afin d’avoir un suivi précis de l’évolution du code et des différentes versions de l’application, il est recommandé d’incrémenter le version code à chaque déploiement. Pour être sûr de ne pas oublier, vous pouvez créer une tâche gradle qui incrémente le numéro de version de 1 à chaque appel.

Voici son code, à insérer dans le build.gradle :

void bumpVersionCodeInFile(File file) { def text = file.text def matcher = (text =~ /versionCode ([0-9]+)/) if (matcher.size() != 1 || !matcher.hasGroup()) { throw new GradleException("Could not find versionCode in app/build.gradle") } def String versionCodeStr = matcher[0][1] def versionCode = Integer.valueOf(versionCodeStr) def newVersionCode = versionCode + 1 def newContent = matcher.replaceFirst("versionCode " + newVersionCode) file.write(newContent) } task(bumpVersionCode) << { def appGradleFile = file('app/build.gradle') if (appGradleFile.canRead()) { bumpVersionCodeInFile(appGradleFile) } else { throw new GradleException("Could not read app/build.gradle"); } def wearGradleFile = file('wear/build.gradle') if (wearGradleFile.canRead()) { bumpVersionCodeInFile(wearGradleFile) } // No exception here since projects are not required to have a wearable app }

Lors de chaque build à partir du moteur d’intégration, on pourra appeler la task bumpVersionCode qui augmente le numéro de version de l’app (et aussi du projet Android Wear dans l’exemple).

No items found.
Pour aller plus loin :