Очень быстро понимаем Android Build Flavors

от автора

Build Flavors — технология, позволяющая собирать несколько вариантов приложения с общей кодовой базой и общими ресурсами.

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

Зачем используют Flavors

Flavors — ваш выбор, если:

  • вам нужно 2 версии приложения — с базовым и расширенным функционалом;

  • вам нужно несколько версий приложения с разным оформлением или частично отличающимся функционалом.

Flavors — не лучший выбор, если вам нужно просто иметь несколько вариантов сборки приложения (с включенной и отключенной обфускацией, например). В этом случае смотрите в сторону Build Types (дефолтные: debug, release)

Общая концепция

Идея очень схожа с модным сейчас Kotlin Multiplatform — у вас есть общий код и ресурсы, лежащие в папке src/main, а также код и ресурсы, которые определены отдельно для каждой версии (каждого flavor).

Структура проекта с двумя Flavor (версиями сборки) - auidioBook и eBook
Структура проекта с двумя Flavor (версиями сборки) — auidioBook и eBook
Например

Например, у вас может быть два приложения про животных — про коней и про рыб. Функционал обоих — одна Activity, которая отображает картинку животного.

Вы создаете два Flavors — AnimalHorse и AnimalFish

В папке src/main/java будет лежать код самой Activity, которая выводит drawable с именем animal (R.drawable.animal). А в папках src/animalHorse/res/drawable и src/animalFish/res/drawable будут лежать картинки лошади и рыбы соответственно. При этом они должны иметь одинаковое имя = animal.

Когда вы собираете apk, в него кладутся ресурсы только для выбранного Flavor — остальные просто игнорируются.

Практика

Пошагово создадим и наполним два Flavors, например, animalHorse и animalFish.

Создаем Flavors

В файле build.gradle (уровня module) необходимо объявить flavorDimensions — группу, объединяющую наши flavors (обязательно):

android { ...     flavorDimensions "animals"     ... }

Дальше добавляем в блок android блок productFlavors, объявляем наши flavors и обязательно прописываем, к какой группе они относятся.

android { ...     flavorDimensions "animals"   productFlavors {         animalHorse {             dimension "animals"         }         animalFish {             dimension "animals"         }     }     ... }

Пересобираем проект (Build -> Rebuild Project).

Заходим в раздел Build -> Select Build Variants…. И видим, что появилось 4 варианта сборки:

  • animalHorseDebug

  • animalHorseRelease

  • animalFishDebug

  • animalFishRelease

То есть у нас всегда будет n = Build Types * Build Flavors вариантов сборки проекта.

Можно выбрать любой из них, после чего студия сделает rebuild, чтобы собрать проект только из тех файлов, которые относятся к выбранной версии.

Переопределяем applicationId

Во-первых, уточню, что applicationId != packageName (packageName = имя папок, в которых лежит проект. у всех flavors должен быть одинаковый packageName). Пост об этом в моем tg-канале c картинками: https://t.me/dolgo_polo_dev/45

Во-вторых, вы можете либо переопределить applicationId для каждого flavors полностью, либо можете добавить applicationIdSuffix для каждого flavor.

android {     defaultConfig {         applicationId "my.app"     } ...     flavorDimensions "animals"   productFlavors {         animalHorse {             dimension "animals"             applicationId "my.app.horse"         }         animalFish {             dimension "animals"             applicationIdSuffix ".fish"         }     }     ... }

Во втором варианте applicationId = applicationId (из блока defaultConfig) + applicationIdSuffix

Создаем разделы с кодом

На данный момент (10.12.2021) Android Studia сам не умеет создавать папки по объявленным Flavors, поэтому создадим их вручную.

p.s. не обязательно создавать папки и файлы, которые не собираетесь переопределять

  1. Откройте структуру проекта в режиме Project.

  2. Создайте в папке app/src папки animalHorse и animalFish.

  3. В созданных папках создайте папки java/my/app/com (если packageName != «my.app.com», то папки внутренние папки будут называться по-другому), res/layout, res/drawable… (необязательно)

  4. В папках animalHorse и animalFish создайте файл AndroidManifest.xml (обязательно)

Готово. Теперь вы можете писать общий код в папке src/main и разный код в папках src/animalHorse и src/animalFish.

Например, вы можете в общем коде хранить Activity, которая выводит на весь экран Fragment с именем FragmentListAnimals. А сам фрагмент определить в обоих версиях приложения отдельно — создайте класс FragmentListAnimals и в папке src/animalHorse/java/my/app/com/fragments, и в папке src/animalFish/java/my/app/com/fragments.

Названия папок и файлов (классов) и их иерархия в папках src/main/java и src/animalHorse/java и src/animalFish/java должны совпадать.

Об AndroidManifest

Как писалось выше, AndroidManifest.xml должен быть создан во всех трех папках — src/main, src/animalFish, src/animalHorse. При этом манифесты в папках src/animalFish и src/animalHorse могут быть пустые (содержать только тег <manifest/>).

Но если вам, например, нужно разрешение на доступ к камере только в одной версии приложения, то можете отредактировать манифест в нужной папке. При сборке приложения этот манифест будет смерджен с основным манифестом, лежащем в scr/main.

Правила соединения манифестов хорошо описаны тут.

Создание BuildConfig переменных

Вы можете создавать константы, которые меняются в зависимости от выбранной сборки. Для этого в gradle-файле создайте buildConfigField для обоих flavor:

android {     defaultConfig {         applicationId "my.app"     } ...     flavorDimensions "animals"   productFlavors {         animalHorse {             dimension "animals"             applicationIdSuffix ".horse"             buildConfigField "String", "URL", "\"https://animals.horse.ru\""         }         animalFish {             dimension "animals"             applicationId "my.app.fish"             buildConfigField "String", "URL", "\"https://animals.fish.ru\""         }     }     ... }

Теперь в любом месте кода вы можете сослаться на эту константу:

 private fun initRetrofit() {        ...         val retrofit = Retrofit.Builder()             .baseUrl(BuildConfig.URL)             .build()  }

Советы и возможные трудности

  1. Скорее всего, начав работать с flavors, вы увидите, насколько ваш код недостаточно разбит на классы. И вам придется провести приличное код-ревью, которое позволит вам переопределять минимум кода для каждой из версий приложения.

  2. Очевидный фокап, словленный мной — когда будете создавать папки для каждой версии, убедитесь, что создали вложенные папки, а не одну. Если пакет называется my.app.com, то должны быть папки my -> app -> com, а не одна с названием my.app.com.

  3. Используйте Dagger/Hilt для внедрения зависимостей. Так будет намного легче упорядочивать разные реализации для разных версий.

  4. Полный список build-параметров, которые можно переопределить для каждого flavor, можно найти в оф. документации (например, minSdkVersion, versionCode…)

А более короткие посты про мобильную разработку с картинками можно почитать туть.


ссылка на оригинал статьи https://habr.com/ru/post/597813/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *