Lorsque vous travaillez sur un projet Android, qui est une plate-forme pour créer des applications de visualisation de contenu vidéo, il est devenu nécessaire de configurer dynamiquement les saveurs du produit avec le transfert d'informations sur la signature des configurations dans un fichier externe. Détails sous la coupe.
Données source
Il existe un projet Android, qui est une plate-forme pour créer des applications de visualisation de contenu vidéo. La base de code est commune à toutes les applications, les différences résident dans les réglages des paramètres de l'API REST et les réglages d'apparence de l'application (bannières, couleurs, polices, etc.). Trois dimensions de saveur ont été utilisées dans le projet:
- marché : google ou amazon. Parce que les applications sont distribuées à la fois sur Google Play et sur Amazon Marketplace, il est nécessaire de partager certaines fonctionnalités en fonction du lieu de distribution. Par exemple: Amazon interdit l'utilisation du mécanisme d'achats intégrés de Google et requiert la mise en œuvre de son mécanisme.
- endpoint : "pro" ou "staging". Configurations spécifiques pour les versions de production et de transfert.
- site : la dimension réelle d'une application particulière. Définissez applicationId et signatureConfig.
Les problèmes que nous avons rencontrés
Lors de la création d'une nouvelle application, il était nécessaire d'ajouter la saveur du produit:
application1 { dimension 'site' applicationId 'com.damsols.application1' signingConfig signingConfigs.application1 }
En outre, il était nécessaire d'ajouter la configuration de signature appropriée:
application1 { storeFile file("path_to_keystore1.jks") storePassword "password1" keyAlias "application1" keyPassword "password1" }
Les problèmes:
- cinq lignes pour ajouter une application, ne différant que par applicationId et signatureConfig. Lorsque le nombre d'applications est passé à plus de 50, le fichier build.gradle a commencé à contenir plus de 500 lignes d'informations sur les applications.
- stockage en texte brut d'informations sur le magasin de clés pour la signature des applications.
Exemple de build.gradle apply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { minSdkVersion 23 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false } } flavorDimensions "site", "endpoint", "market" signingConfigs { application1 { storeFile file("application1.jks") storePassword "password1" keyAlias "application1" keyPassword "password1" } application2 { storeFile file("application2.jks") storePassword "password2" keyAlias "application2" keyPassword "password2" } application3 { storeFile file("application3.jks") storePassword "password3" keyAlias "application3" keyPassword "password3" } } productFlavors { pro { dimension 'endpoint' } staging { dimension 'endpoint' } google { dimension 'market' } amazon { dimension 'market' } application1 { dimension 'site' applicationId "com.damsols.application1" signingConfig signingConfigs.application1 } application2 { dimension 'site' applicationId "com.damsols.application2" signingConfig signingConfigs.application2 } application3 { dimension 'site' applicationId "com.damsols.application3" signingConfig signingConfigs.application3 } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' }
La première étape a été de transférer les informations du certificat dans un fichier json distinct. Par exemple, les informations sont également stockées en texte brut, mais rien ne vous empêche de stocker le fichier sous forme cryptée (nous utilisons GPG) et de le décrypter directement lors de la création de l'application. Le fichier JSON a la structure suivante:
{ "signingConfigs":[ { "configName":"application1", "storeFile":"application1.jks", "storePassword":"password1", "keyAlias":"application1", "keyPassword":"password1" }, { "configName":"application2", "storeFile":"application2.jks", "storePassword":"password2", "keyAlias":"application2", "keyPassword":"password2" }, { "configName":"application3", "storeFile":"application3.jks", "storePassword":"password3", "keyAlias":"application3", "keyPassword":"password3" }, ] }
La section signatureConfigs du fichier build.gradle est supprimée.
Simplifier les sections sur les saveurs des produits
Pour réduire le nombre de lignes nécessaires pour décrire la saveur du produit avec dimension = "site", un tableau a été créé avec les informations nécessaires pour décrire une application spécifique, et toutes les saveurs de produit avec dimension = "site" ont été supprimées.
C'était:
... productFlavors { pro { dimension 'endpoint' } staging { dimension 'endpoint' } google { dimension 'market' } amazon { dimension 'market' } application1 { dimension 'site' applicationId "com.damsols.application1" signingConfig signingConfigs.application1 } application2 { dimension 'site' applicationId "com.damsols.application2" signingConfig signingConfigs.application2 } application3 { dimension 'site' applicationId "com.damsols.application3" signingConfig signingConfigs.application3 } } } ...
C'est devenu:
... productFlavors { pro { dimension 'endpoint' } staging { dimension 'endpoint' } google { dimension 'market' } amazon { dimension 'market' } } def applicationDefinitions = [ ['name': 'application1', 'applicationId': 'com.damsols.application1'], ['name': 'application2', 'applicationId': 'com.damsols.application2'], ['name': 'application3', 'applicationId': 'com.damsols.application3'] ] } ...
Création de saveurs de produits dynamiques
La dernière étape consistait à créer dynamiquement des versions de produit et à signer des configurations à l'aide d'un fichier JSON externe avec des informations de certificat provenant du tableau applicationDefinitions.
def applicationDefinitions = [ ['name': 'application1', 'applicationId': 'com.damsols.application1'], ['name': 'application2', 'applicationId': 'com.damsols.application2'], ['name': 'application3', 'applicationId': 'com.damsols.application3'] ] def signKeysFile = file('signkeys/signkeys.json') def signKeys = new JsonSlurper().parseText(signKeysFile.text) def configs = signKeys.signingConfigs def signingConfigsMap = [:] configs.each { config -> signingConfigsMap[config.configName] = config } applicationDefinitions.each { applicationDefinition -> def signingConfig = signingConfigsMap[applicationDefinition['name']] android.productFlavors.create(applicationDefinition['name'], { flavor -> flavor.dimension = 'site' flavor.applicationId = applicationDefinition['applicationId'] flavor.signingConfig = android.signingConfigs.create(applicationDefinition['name']) flavor.signingConfig.storeFile = file(signingConfig.storeFile) flavor.signingConfig.storePassword = signingConfig.storePassword flavor.signingConfig.keyAlias = signingConfig.keyAlias flavor.signingConfig.keyPassword = signingConfig.keyPassword }) }
Pour ajouter une lecture à partir d'un stockage chiffré, vous devez remplacer la section
def signKeysFile = file('signkeys/signkeys.json') def signKeys = new JsonSlurper().parseText(signKeysFile.text) def configs = signKeys.signingConfigs
pour lire à partir d'un fichier crypté.
build.gradle entier import groovy.json.JsonSlurper apply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { minSdkVersion 23 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false } } flavorDimensions "site", "endpoint", "market" signingConfigs {} productFlavors { pro { dimension 'endpoint' } staging { dimension 'endpoint' } google { dimension 'market' } amazon { dimension 'market' } } } def applicationDefinitions = [ ['name': 'application1', 'applicationId': 'com.damsols.application1'], ['name': 'application2', 'applicationId': 'com.damsols.application2'], ['name': 'application3', 'applicationId': 'com.damsols.application3'] ] def signKeysFile = file('signkeys/signkeys.json') def signKeys = new JsonSlurper().parseText(signKeysFile.text) def configs = signKeys.signingConfigs def signingConfigsMap = [:] configs.each { config -> signingConfigsMap[config.configName] = config } applicationDefinitions.each { applicationDefinition -> def signingConfig = signingConfigsMap[applicationDefinition['name']] android.productFlavors.create(applicationDefinition['name'], { flavor -> flavor.dimension = 'site' flavor.applicationId = applicationDefinition['applicationId'] flavor.signingConfig = android.signingConfigs.create(applicationDefinition['name']) flavor.signingConfig.storeFile = file(signingConfig.storeFile) flavor.signingConfig.storePassword = signingConfig.storePassword flavor.signingConfig.keyAlias = signingConfig.keyAlias flavor.signingConfig.keyPassword = signingConfig.keyPassword }) } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' }
Lien GitHub
Je vous remercie!