Android: création de saveurs de produits dynamiques et signature de configurations

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:


  1. 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.
  2. endpoint : "pro" ou "staging". Configurations spécifiques pour les versions de production et de transfert.
  3. 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:


  1. 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.
  2. 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' } 

Suppression des informations de certificat


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!

Source: https://habr.com/ru/post/fr434994/


All Articles