Ao trabalhar em um projeto Android, que é uma plataforma para criar aplicativos para exibição de conteúdo de vídeo, tornou-se necessário configurar dinamicamente os sabores do produto com a transferência de informações sobre como assinar configurações para um arquivo externo. Detalhes sob o corte.
Dados de origem
Existe um projeto Android, que é uma plataforma para a criação de aplicativos para visualização de conteúdo de vídeo. A base de código é comum a todos os aplicativos, as diferenças estão nas configurações dos parâmetros da API REST e nas configurações de aparência do aplicativo (banners, cores, fontes, etc.). Três dimensões de sabor foram usadas no projeto:
- mercado : google ou amazon. Porque Como os aplicativos são distribuídos no Google Play e no Amazon Marketplace, é necessário compartilhar algumas funcionalidades, dependendo do local de distribuição. Por exemplo: a Amazon proíbe o uso do mecanismo de compras no aplicativo do Google e requer a implementação de seu mecanismo.
- ponto final : "pro" ou "preparo". Configurações específicas para versões de produção e preparação.
- site : a dimensão real para uma aplicação específica. Defina applicationId e signatureConfig.
Os problemas que encontramos
Ao criar um novo aplicativo, foi necessário adicionar o Sabor do Produto:
application1 { dimension 'site' applicationId 'com.damsols.application1' signingConfig signingConfigs.application1 }
Além disso, era necessário adicionar a configuração de assinatura apropriada:
application1 { storeFile file("path_to_keystore1.jks") storePassword "password1" keyAlias "application1" keyPassword "password1" }
Os problemas:
- cinco linhas para adicionar um aplicativo, diferindo apenas em applicationId e signatureConfig. Quando o número de aplicativos se tornou mais de 50, o arquivo build.gradle começou a conter mais de 500 linhas de informações do aplicativo.
- armazenamento em informações de texto sem formatação sobre o keystore para aplicativos de assinatura.
Exemplo 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' }
A primeira etapa foi transferir as informações do certificado para um arquivo json separado. Por exemplo, as informações também são armazenadas em texto sem formatação, mas nada impede que você armazene o arquivo em formato criptografado (usamos GPG) e descriptografá-lo diretamente durante a criação do aplicativo. O arquivo JSON tem a seguinte estrutura:
{ "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" }, ] }
A seção signatureConfigs no arquivo build.gradle é excluída.
Seções Simplificar sabores de produtos
Para reduzir o número de linhas necessárias para descrever o Flavour do produto com dimension = "site", foi criada uma matriz com as informações necessárias para descrever um aplicativo específico, e todos os Flavours do produto com dimension = "site" foram excluídos.
Foi:
... 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 } } } ...
Tornou-se:
... 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'] ] } ...
Criação dinâmica de sabores de produtos
A última etapa foi criar dinamicamente tipos de produtos e assinar configurações usando um arquivo JSON externo com informações de certificado da matriz 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 adicionar leitura do armazenamento criptografado, é necessário substituir a seção
def signKeysFile = file('signkeys/signkeys.json') def signKeys = new JsonSlurper().parseText(signKeysFile.text) def configs = signKeys.signingConfigs
para ler de um arquivo criptografado.
build.gradle whole 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' }
Link do GitHub
Obrigada