Введение: вопрос «что такое demoDebug»
«У вас в проекте есть demoDebug и fullRelease — что это за сборки?» — на этом вопросе половина кандидатов теряется. Годы нажатий на кнопку Run в Android Studio не помогают: человек не знает, что именно он собирает и почему у одного проекта так много «версий».
Build variant — это одна из версий приложения, которую можно собрать из одного проекта. Например, бесплатная версия с ограниченным функционалом и платная с полным набором возможностей. Без понимания вариантов сборки вы не ответите на целый веер смежных вопросов: почему debug-версию можно поставить на устройство рядом с release, откуда у одного проекта два applicationId и куда класть код, который должен попасть только в платную версию.
Хорошая новость: build variants не настраиваются напрямую. Вы настраиваете build types и product flavors, а Gradle сам комбинирует их по фиксированным правилам и называет результат по схеме <флейвор><БилдТайп>: demoDebug, demoRelease, fullDebug, fullRelease. Всё это описано в официальном гайде Configure build variants — по нему и разберём тему.
Build types: debug и release
Build type — это настройки сборки и упаковки: отладочная информация, подпись, обфускация, суффиксы. При создании модуля Android Studio автоматически создаёт два build type: debug и release.
Debug-тип в файле не виден, но существует: у него включена отладка (isDebuggable = true), и подписан он отладочным ключом — debug keystore. Release — это то, что уходит в Google Play: обычно обфускация и минификация через R8 и релизная подпись.
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android.txt"),
"proguard-rules.pro"
)
}
getByName("debug") {
applicationIdSuffix = ".debug"
}
create("staging") {
initWith(getByName("debug"))
applicationIdSuffix = ".debugStaging"
}
}
}В примере два приёма, которые любят спрашивать:
applicationIdSuffix = ".debug"— добавляет суффикс к applicationId. Debug-версия получает идентификаторcom.example.myapp.debugи может стоять на устройстве рядом с release: у двух приложений не может совпадать applicationId.initWith— копирует конфигурацию другого build type и позволяет её изменить. Так создают промежуточные типы вродеstaging— это реальный пример из документации.
Классическая ошибка: сказать «debug — для отладки, release — для релиза» и остановиться. Интервьюер ждёт деталей: debuggable, подпись, applicationIdSuffix — и понимания, что эти настройки влияют на итоговый вариант сборки, а не только на его имя.
Product flavors: сорта приложения
Product flavor — это «сорт» приложения: бесплатное и платное, клиент A и клиент B, версия с рекламой и без. Флейворы настраиваются так же, как defaultConfig, и это не совпадение: defaultConfig на самом деле принадлежит классу ProductFlavor, поэтому флейвору доступны все те же свойства — applicationId, versionName, minSdk и остальные.
android {
flavorDimensions += "version"
productFlavors {
create("demo") {
dimension = "version"
applicationIdSuffix = ".demo"
versionNameSuffix = "-demo"
}
create("full") {
dimension = "version"
applicationIdSuffix = ".full"
versionNameSuffix = "-full"
}
}
}Флейвор без измерения — ошибка сборки
Все флейворы обязаны принадлежать flavor dimension — группе флейворов. Если измерение одно, AGP назначит его автоматически. Но как только измерений несколько (например, version и api), указывать dimension нужно явно: Gradle комбинирует по одному флейвору из каждого измерения, а флейворы из одного измерения — никогда. Два флейвора и два билд-тайпа дают четыре варианта: demoDebug, demoRelease, fullDebug, fullRelease.
Зачем флейворы на практике:
- Разные applicationId — бесплатная и платная версия выходят как отдельные приложения в Google Play.
- Разные зависимости — конфигурации
demoImplementation(...)иfullImplementation(...)в блоке dependencies: платные модули попадают только в платную версию. - Variant-aware dependency matching — Gradle умеет подтягивать из локальных библиотек именно тот вариант, который совпадает с флейвором приложения. Поэтому имена измерений выбирают осознанно: от них зависит, какие код и ресурсы библиотеки окажутся в сборке.
Когда измерений несколько, важен порядок их объявления: flavorDimensions += listOf("version", "api") — первое измерение имеет высший приоритет при слиянии кода и ресурсов варианта. Комбинация получается из флейвора каждого измерения: скажем, demo из «version» и minApi24 из «api» дадут вариант demoMinApi24Debug. Именно такой расклад спрашивают на позиции senior: кандидат, который впервые видит два измерения, обычно не может предсказать имя варианта и объяснить, откуда берётся приоритет.
Source sets: как развести код по вариантам
Вся механика «разных приложений из одного проекта» — в source sets: каталогах с кодом и ресурсами, которые Gradle собирает в зависимости от варианта.
По умолчанию всё лежит в src/main/ — это общий код для всех вариантов. Для конкретных сборок создают отдельные каталоги, повторяющие структуру main:
app/src/
├── main/ # общий код и ресурсы
├── debug/ # только для debug-сборок
├── release/ # только для release
├── demo/ # только для флейвора demo
└── demoDebug/ # только для варианта demoDebugGradle ждёт Kotlin-код в src/debug/kotlin или src/debug/java, манифест — в src/debug/AndroidManifest.xml, ресурсы — в src/debug/res. Source sets комбинаций флейворов (например, src/demoMinApi24/) имеют более высокий приоритет, чем source set отдельного флейвора: чем специфичнее вариант, тем сильнее его код перекрывает общий.
Типичные применения:
src/debug/AndroidManifest.xml— права и компоненты только для отладки.src/demo/res/— другой брендинг, иконки и строки для демо-версии.src/release/kotlin/— классы, которые должны попасть только в релиз.
Практика: варианты в реальном проекте
Выбрать, что собирать, можно в Android Studio: Build → Select Build Variant — или из командной строки: ./gradlew assembleDemoDebug соберёт конкретный вариант, а ./gradlew tasks покажет список всех доступных задач. Имя задачи повторяет имя варианта, поэтому даже не глядя в конфигурацию видно, какие комбинации существуют.
Покажу, как это выглядит в реальном проекте. Допустим, у вас приложение-читалка: бесплатная версия с рекламой и платная без. Конфигурация будет примерно такой: два флейвора free и pro в измерении version, два билд-тайпа по умолчанию. Из этого получаются четыре варианта — freeDebug, freeRelease, proDebug, proRelease. В src/free/res/ лежит строка с именем приложения и layout с баннером рекламы, в src/pro/res/ — без. freeImplementation("com.example:ads-sdk:1.0") подтягивает SDK рекламы только в бесплатную сборку. Один код, один репозиторий — четыре артефакта, два из которых попадают в Google Play как отдельные приложения с разными applicationId.
Есть и более тонкий момент, который ценят на собеседованиях: варианты живут не только в приложении, но и в тестах. Инструментальные тесты можно класть в src/androidTestDemoDebug/ — тогда они выполняются только для конкретного варианта. А вот что тестировать на каждом варианте — вопрос уже к стратегии тестирования, но факт, что сборка влияет даже на тесты, показывает глубину понимания темы.
На собеседованиях по этой теме я чаще всего вижу три промаха:
- «Флейвор — это то же самое, что build type» — нет: билд-тайп отвечает за «как собираем» (отладка, подпись), флейвор — за «что собираем» (какой сорт приложения).
- «Все варианты обязаны выходить в Google Play» — нет, debug и staging собирают для разработки, а вариант можно исключить из сборки, если он не нужен.
- «Source sets — это сложно» — на деле это просто каталоги: добавили
src/demo/kotlin/— и код в нём попадёт только в demo-варианты.
Тема сборки — одна из самых предсказуемых: здесь всё по правилам и без сюрпризов, в отличие от жизненного цикла и потоков. Если закрыть её один раз, вопросы из неё на собеседованиях перестанут пугать. А заодно перестанет пугать и сам Gradle — об этом мы уже разбирали в гайде по build.gradle.
Хотите системно подготовиться к собеседованию?
Если темы плавают — от сборки до архитектуры — дело не в способностях, а в отсутствии системы. Бесплатная диагностика: разберу ваши пробелы и составлю план роста за 30–40 минут, без обязательств.
Итоги
Build variants — это не магия, а комбинаторика: билд-тайп задаёт «как собираем», флейвор — «что собираем», а source sets разводят код по вариантам.
- Build types — debug и release: отладка, подпись, обфускация, суффиксы.
- Product flavors — сорта приложения с разными applicationId, версиями и зависимостями.
- Source sets — каталоги
src/<вариант>/, которые Gradle сливает: чем специфичнее вариант, тем выше приоритет его кода.
Главное, что нужно запомнить
Из одного проекта можно собрать любое количество вариантов: добавьте флейворы и билд-тайпы, и Gradle сам соберёт комбинации, назвав их по правилу <флейвор><БилдТайп>. Умеете объяснить, откуда взялся demoDebug, — тема закрыта.
Если после прочтения поняли, что свою сборку объясняете пока на пальцах, — не нужно зубрить. Напишите в Telegram, разберём вашу ситуацию и составим план, с чего начать подготовку.