Android: Crear sabores dinámicos de productos y firmar configuraciones

Al trabajar en un proyecto de Android, que es una plataforma para crear aplicaciones para ver contenido de video, se hizo necesario configurar dinámicamente los sabores de los productos con la transferencia de información sobre las configuraciones de firma a un archivo externo. Detalles debajo del corte.


Datos de origen


Hay un proyecto de Android, que es una plataforma para crear aplicaciones para ver contenido de video. La base del código es común para todas las aplicaciones, las diferencias están en la configuración de los parámetros de la API REST y en la configuración de la apariencia de la aplicación (pancartas, colores, fuentes, etc.). Se utilizaron tres dimensiones de sabor en el proyecto:


  1. mercado : google o amazon. Porque las aplicaciones se distribuyen tanto en Google Play como en Amazon Marketplace, es necesario compartir algunas funciones según el lugar de distribución. Por ejemplo: Amazon prohíbe el uso del mecanismo de compras en la aplicación de Google y requiere la implementación de su mecanismo.
  2. punto final : "pro" o "puesta en escena". Configuraciones específicas para versiones de producción y puesta en escena.
  3. sitio : la dimensión real para una aplicación particular. Establezca applicationId y SignatureConfig.

Los problemas que encontramos


Al crear una nueva aplicación, era necesario agregar el sabor del producto:


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

Además, fue necesario agregar la configuración de firma adecuada:


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

Los problemas:


  1. cinco líneas para agregar una aplicación, que difieren solo en applicationId y SignatureConfig. Cuando el número de aplicaciones se convirtió en más de 50, el archivo build.gradle comenzó a contener más de 500 líneas de información de la aplicación.
  2. almacenamiento en información de texto plano sobre el almacén de claves para firmar aplicaciones.

Ejemplo 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' } 

Eliminar información del certificado


El primer paso fue transferir la información del certificado a un archivo json separado. Por ejemplo, la información también se almacena en texto plano, pero nada le impide almacenar el archivo en forma cifrada (usamos GPG) y descifrarlo directamente durante la compilación de la aplicación. El archivo JSON tiene la siguiente estructura:


 { "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 sección signConfigs en el archivo build.gradle se elimina.


Simplifique las secciones de sabores de productos


Para reducir el número de líneas requeridas para describir el sabor del producto con dimensión = "sitio", se creó una matriz con la información necesaria para describir una aplicación específica, y se eliminaron todos los sabores del producto con dimensión = "sitio".
Fue:


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

Se convirtió en:


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

Creación dinámica de sabores de productos


El último paso fue crear dinámicamente sabores de productos y configuraciones de firma utilizando un archivo JSON externo con información de certificado de la matriz de 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 }) } 

Para agregar la lectura del almacenamiento encriptado, debe reemplazar la sección


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

leer desde un archivo encriptado.


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

Enlace GitHub


Gracias

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


All Articles