Android: Создание Динамических Вариантов Продукта И Подписание Конфигураций

При работе над Android-проектом, представляющим собой платформу для создания приложений для просмотра видеоконтента, возникла необходимость динамической настройки продуктовых вкусов с передачей информации о подписании конфигов во внешний файл.

Подробности под катом.



Исходные данные

Существует Android-проект, который представляет собой платформу для создания приложений для просмотра видеоконтента.

Кодовая база общая для всех приложений, различия заключаются в настройках параметров REST API и настройках внешнего вида приложения (баннеры, цвета, шрифты и т.д.).

В проекте используются три вкусовых измерения:

  1. рынок: «Google» или «amazon».

    Поскольку приложения распространяются как в Google Play, так и на Amazon Marketplace, возникает необходимость разделения некоторого функционала в зависимости от места распространения.

    Например: Amazon запрещает использование механизма Google In-App Purchases и требует внедрения собственного механизма.

  2. конечная точка: «про» или «постановка».

    Особые конфигурации для производственных и промежуточных версий.

  3. сайт: фактический размер для конкретного приложения.

    ApplicationId и SigningConfig установлены.



Проблемы, с которыми мы столкнулись

При создании нового приложения необходимо было добавить Product Flavor:
  
  
  
  
  
  
  
  
   

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

Также необходимо было добавить соответствующую конфигурацию подписи:

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



Проблемы:

  1. пять строк для добавления одного приложения, отличающегося только applicationId и SigningConfig. Когда количество приложений стало больше 50, файл build.gradle стал содержать более 500 строк информации о приложениях.

  2. хранение в открытом виде информации о хранилище ключей для подписи приложений.

Пример 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' }



Отображение информации о сертификатах

Первым шагом было перенести информацию о сертификатах в отдельный json-файл.

Например, информация также хранится в открытом виде, но ничто не мешает вам сохранить файл в зашифрованном виде (мы используем GPG) и расшифровать его непосредственно во время сборки приложения.

Файл JSON имеет следующую структуру:

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

Удаляем раздел SigningConfigs в файле build.gradle.

Упрощение раздела «Вкусы продуктов»

Чтобы уменьшить количество строк, необходимых для описания варианта продукта с измерением = «сайт», был создан массив с необходимой информацией для описания конкретного приложения, а все варианты продукта с измерением = «сайт» были удалены.

Был:

.

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



Стал:

.

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





Динамическое создание вкусов продукта

Последним шагом было динамическое создание разновидностей продуктов и конфигураций подписи с использованием внешнего файла JSON с информацией о сертификатах из массива 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 }) }

Чтобы добавить чтение из зашифрованного хранилища, необходимо заменить раздел

def signKeysFile = file('signkeys/signkeys.json') def signKeys = new JsonSlurper().

parseText(signKeysFile.text) def configs = signKeys.signingConfigs

для чтения из зашифрованного файла.

build.gradle целиком

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

Ссылка на Гитхаб Спасибо! Теги: #Android #Разработка Android #Разработка мобильных приложений #gradle #динамические варианты

Вместе с данным постом часто просматривают: