воскресенье, 3 января 2016 г.

Gradle Product Flavors. Основные понятия и структура проекта

Рассмотрим распространённую задачу: есть приложение, у которого есть две версии — ограниченная и полная. Они выпускаются с разными applicationId, у каждого своя иконка, и в ограниченной отсутствует некоторая функциональность.

Что будем делать?

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

По сути задачи у нас следующие:

  • Использовать для сборок разные ресурсы
  • Настроить свой applicationId для каждой сборки

Попробуем всё это сделать.

BuildType и ProductFlavor

Все видели buildTypes в грэдловском билд-скрипте. Более-менее понятно, зачем нужны сборки debug и release: в дебаге отключаем статистику и краш-репорты, зато включаем логи, в релизе наоборот.

Понятие flavor кажется очень похожим. Я формулирую отличие так: BuildType — это тип сборки, и он используется только при разработке, а ProductFlavor — вариация приложения, и она уже имеет значение для пользователя. Так что debug/release — это BuildType, а free/premium — ProductFlavor. А ещё вариации можно объединять и получать что-то вроде app-samsung-free или app-amazon-premium (об этом будет в следующей части), а типы билдов несочетаемы.

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

Вариации и структура проекта

Вариации можно определить в билд-скрипте в разделе productFlavors. Для нашего случая всё начинается с этого:

app/build.gradle

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"
    defaultConfig {
        applicationId "com.demos.productflavors"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

productFlavors { free { } premium { } }
}

Определить ресурсы и логику, специфичные для вариации, довольно просто. Просто создаём в папке src подпапки free и premium, реализуем ту же структуру, что и в main — и готово. Например, чтобы сделать у free и premium разные иконки, надо всего лишь рассовать нужные картинки по mipmap-папочкам:

Структура проекта

Для отключения части функциональности во free-версии тоже можно обойтись ресурсами: создать values.xml, завести там флаг is_premium и проверять его везде, где хочется что-нибудь ограничить. А можно заняться архитектурными излишествами и наворотить кода, но об этом в следующий раз.

Application Id

Тут всё очевидно. В объектной модели у ProductFlavor есть applicationId, мы его указываем и получаем две независимые сборки.

productFlavors {
    premium {
        applicationId "com.demos.productflavors.premium"
    }

    free {
        applicationId "com.demos.productflavors"
    }
}

Про разницу между application id и package name можно почитать в документации к Android Build Tools. Вкратце, application id используется извне (при публикации, при размещении на устройстве и т.д.), а package name — при разработке (импорт класса R).

На этом пока всё. В следующей части рассмотрим более сложные задачи.

Код к обеим частям

2 комментария:

Пожалуйста, пишите содержательные комментарии и соблюдайте вежливость.