Введение: вопрос «что такое 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 и релизная подпись.

app/build.gradle.kts
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 и остальные.

app/build.gradle.kts
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/
app/src/
├── main/          # общий код и ресурсы
├── debug/         # только для debug-сборок
├── release/       # только для release
├── demo/          # только для флейвора demo
└── demoDebug/     # только для варианта demoDebug

Gradle ждёт 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, разберём вашу ситуацию и составим план, с чего начать подготовку.