вторник, 2 апреля 2019 г.

Android Studio 3.3 и «ароматы». Создание разных вариантов программ на одном исходнике.

На днях у меня возникла задача сделать три одинаковых андроид-приложения, которые различались бы только одним строковым ресурсом и иконкой, и которые можно было бы одновременно поставить на одном смартфоне.Можно было бы просто написать в коде все три строчки, и комментируя каждый раз ненужные, компилировать три варианта приложения. Ну, конечно не забывать при этом делать тоже самое в android:label в манифесте, чтобы менять название приложений и applicationId в build.gradle модуля app, чтобы менять название пакета приложения. Иначе одна программа, будет при установке затирать другую. Ну и иконку, конечно, каждый раз придётся переделывать.

Через год можно уже и не вспомнить, где что надо менять. Как вариант, можно сделать три параллельных проекта. Но тогда при изменении одного придётся менять и остальные. В общем, должен быть другой путь. И такой путь есть.

Делаем варианты приложений на основе единого кода и ресурсов.Такие варианты в терминологии Android Studio называются «ароматами».

Создаём структуру ароматов


Нажимаем «File → Project Structure...» или просто сочетание клавиш Ctrl+Alt+Shift+S. Слева выбираем модуль «app» а справа вкладку «Flavors». В первом столбце у нас прописана наша основная конфигурация, называемая «drfaultConfig». Пока она одна, она и является основным и единственным вариантом приложения. Теперь добавляем в этот столбец столько конфигураций, сколько вариантов приложений нам нужно. При этом основная конфигурация становится базой для всех остальных, и на её основе проект строится уже не будет. Теперь проект будет строится на добавленных вариантах (ароматах).

Справа для каждого аромата отображаются поля, которые можно поменять относительно базовой конфигурации. Если их оставить незаполненными, их значения при сборке проекта будут подхватываться из базовой конфигурации «drfaultConfig». В моём случае для каждого из ароматов я просто прописал свой applicationId, чтобы смартфон видел все полученные в итоги приложения, как разные.

Затем открываем build.gradle модуля app, и видим в нём вновь созданную конструкцию:

productFlavors {
    flavor {
    }
    flavor1 {
    }
    flavor2 {
    }
}

Здесь Android Studio автоматизировал построение этого шаблона настроек, но не до конца. Доработаем шаблон следующим образом:

flavorDimensions "default"

productFlavors {
    flavor {
        flavorDimensions "default"
    }
    flavor1 {
        flavorDimensions "default"
    }
    flavor2 {
        flavorDimensions "default"
    }
}

Здесь мы указали, что все три аромата будут находиться в одной группе default. Это простейший вариант. Количество ароматов определятся количеством возможных отношений разных групп друг с другом. Это не просто группы, это как бы «измерения». Об этом вы можете почитать в первоисточнике.

Создаём наполнение ароматов


Открываем обозреватель проекта в режиме Project. Открываем директорию app → src. В этой директории видим директорию main. Это директория содержит директории кода и ресурсов (java и res) базовой конфигурации «drfaultConfig», о которой мы говорили выше. Теперь, рядом с директорией базовой конфигурации main нужно создать директории с именами наших ароматов, в которые длбавить только те ресурсы и код, которые будут отличаться от базовых.

Например, в директорию flavor я не помещаю ничего (можно её вообще не делать), и тогда этот аромат будет полностью повторять исходную конфигурацию. А в две оставшиеся директории flavor1 и flavor2 я кидаю только файлы, которые отличаются. Структуру вложенных директорий сохраняю.

Необязательно всё делать вручную. Например, отличающуюся иконку можно сделать следующим образом, даже предварительно не создавая никаких директорий. Нажимаем в обозревателе проекта правой кнопкой мыши на директории app и из контекстного меню выбираем New → Image Asset. Создаём иконку, нажимаем Next и в поле Res Directory выбираем имя того аромата, для которого эта иконка предназначена. После этого нажимаем Finish. Директория выбранного аромата создаётся автоматически и в неё помещается наша иконка.

То же самое можно проделать с добавлением любого другого ресурса. Надо только не забывать выбирать при создании или сохранении ресурса тот аромат, для которого ресурс предназначен. Везде в любом «мастере» поле выбора аромата присутствует.

Компиляция


Теперь можно осуществить компиляцию и запуск каждого из ароматов, переключаясь между ними через вкладку Build Variants, расположенную в левом нижнем углу Android Studio. Для запуска в эмуляторе или на реальном подключенном к компьютеру устройстве используем Debug-версии.

Для чистовой компиляции apk-файла того или иного аромата с подписью эту вкладку не используем, а выбор аромата осуществляем в окне «Build → Generated Signed Bundle or APK» на заключительной стадии в поле Build Variants. Подписанный apk-файл создастся по указанному в поле выше пути, в подпапке с именем аромата.

Строки, не подлежащие переводу


Ну и, в заключение небольшой нюанс про строковые ресурсы, которые не нужно переводить на другие языки. Например, строки с адресами сайтов или именами приложений. По умолчанию, если строка оставлена без перевода, Android Studio выдаёт ошибку. Чтобы этого не происходило, такие строки надо помечать, как не подлежащие переводу. Это можно сделать так:

<resources>
    <string name="url" translatable="false">https://site.ru</string>
    <string name="labelapp" translatable="false">CoolApp</string>
</resources>

Или даже так:

<resources translatable="false">
    <string name="url">https://site.ru</string>
    <string name="labelapp">CoolApp</string>
</resources>
 

Смена имени подписанного apk-файла


Для того, чтобы переименовать apk-файл после компиляции, добавим в build.gradle модуля app перед блоком ароматов следующий код:

applicationVariants.all { variant ->
        if (variant.productFlavors[0].name.equals("flavor") && variant.buildType.name.equals("release")) {
            variant.outputs.all { output ->
                outputFileName = "myapp01.apk"
            }
        } else if (variant.productFlavors[0].name.equals("flavor1") && variant.buildType.name.equals("release")) {
            variant.outputs.all { output ->
                outputFileName = "myapp02.apk"
            }
        } else if (variant.productFlavors[0].name.equals("flavor2") && variant.buildType.name.equals("release")){
            variant.outputs.all { output ->
                outputFileName = "myapp03.apk"
            }
        }
    }

Он заставит переименовывать все релизные варианты соответствующих ароматов согласно указанным именам.