Android: Erstellen dynamischer Produktaromen und Signieren von Konfigurationen

Bei der Arbeit an einem Android-Projekt, bei dem es sich um eine Plattform zum Erstellen von Anwendungen zum Anzeigen von Videoinhalten handelt, musste die Produktvariante dynamisch konfiguriert werden, indem Informationen zum Signieren von Konfigurationen in eine externe Datei übertragen wurden. Details unter dem Schnitt.


Ausgangsdaten


Es gibt ein Android-Projekt, eine Plattform zum Erstellen von Anwendungen zum Anzeigen von Videoinhalten. Die Codebasis ist für alle Anwendungen gleich. Die Unterschiede bestehen in den Einstellungen der REST-API-Parameter und den Einstellungen für das Erscheinungsbild der Anwendung (Banner, Farben, Schriftarten usw.). Im Projekt wurden drei Geschmacksdimensionen verwendet:


  1. Markt : Google oder Amazon. Weil Anwendungen werden sowohl auf Google Play als auch auf dem Amazon Marketplace verteilt. Abhängig vom Verteilungsort müssen einige Funktionen gemeinsam genutzt werden. Beispiel: Amazon verbietet die Verwendung des In-App-Kaufmechanismus von Google und erfordert die Implementierung seines Mechanismus.
  2. Endpunkt : "Pro" oder "Staging". Spezifische Konfigurationen für Produktions- und Staging-Versionen.
  3. Site : Die tatsächliche Dimension für eine bestimmte Anwendung. Legen Sie die Anwendungs-ID und die Signaturkonfiguration fest.

Die Probleme, auf die wir gestoßen sind


Beim Erstellen einer neuen Anwendung musste Product Flavor hinzugefügt werden:


application1 { dimension 'site' applicationId 'com.damsols.application1' signingConfig signingConfigs.application1 } 

Außerdem musste die entsprechende Signaturkonfiguration hinzugefügt werden:


 application1 { storeFile file("path_to_keystore1.jks") storePassword "password1" keyAlias "application1" keyPassword "password1" } 

Die Probleme:


  1. Fünf Zeilen zum Hinzufügen einer Anwendung, die sich nur in applicationId und signingConfig unterscheiden. Als die Anzahl der Anwendungen mehr als 50 betrug, enthielt die Datei build.gradle mehr als 500 Zeilen mit Anwendungsinformationen.
  2. Speicherung von Informationen zum Keystore zum Signieren von Anwendungen im Klartext.

Build.gradle-Beispiel
 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' } 

Entfernen von Zertifikatinformationen


Der erste Schritt bestand darin, die Zertifikatinformationen in eine separate JSON-Datei zu übertragen. Beispielsweise werden Informationen auch im Klartext gespeichert, aber nichts hindert Sie daran, die Datei in verschlüsselter Form (wir verwenden GPG) zu speichern und sie direkt während der Anwendungserstellung zu entschlüsseln. Die JSON-Datei hat die folgende Struktur:


 { "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" }, ] } 

Der Abschnitt signingConfigs in der Datei build.gradle wird gelöscht.


Vereinfachen Sie die Produktaromenabschnitte


Um die Anzahl der Zeilen zu verringern, die zur Beschreibung des Produktgeschmacks mit dimension = "site" erforderlich sind, wurde ein Array mit den erforderlichen Informationen zur Beschreibung der spezifischen Anwendung erstellt und alle Produktaromen mit dimension = "site" gelöscht.
Es war:


 ... 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 } } } ... 

Es wurde:


 ... 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'] ] } ... 

Dynamische Produktaromenerstellung


Der letzte Schritt bestand darin, mithilfe einer externen JSON-Datei mit Zertifikatinformationen aus dem Array applicationDefinitions dynamisch Produktvarianten zu erstellen und Konfigurationen zu signieren.


 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 }) } 

Um das Lesen aus dem verschlüsselten Speicher hinzuzufügen, müssen Sie den Abschnitt ersetzen


 def signKeysFile = file('signkeys/signkeys.json') def signKeys = new JsonSlurper().parseText(signKeysFile.text) def configs = signKeys.signingConfigs 

aus einer verschlüsselten Datei lesen.


build.gradle ganz
 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' } 

GitHub Link


Vielen Dank!

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


All Articles