Gradle & Build System
Gradle - инструмент сборки Android-проектов. Он разрешает зависимости, настраивает модули, выбирает и выполняет задачи, а затем создаёт артефакты для тестирования или распространения. Android Gradle Plugin (AGP) добавляет Android-специфичные сущности: манифесты, ресурсы, уровни SDK, варианты сборки, подпись, lint и упаковку APK/AAB.
Gradle, AGP и жизненный цикл сборки
Gradle - универсальный движок сборки, а AGP - плагин, который учит его собирать Android-приложения и библиотеки. Плагины Kotlin, KSP, сериализации и другие плагины добавляют собственные задачи и настройки.
Запуск Gradle состоит из трёх основных фаз:
- Инициализация - Gradle читает файл настроек и определяет проекты, участвующие в сборке.
- Конфигурация - выполняет логику сборки, применяет плагины и создаёт или настраивает граф задач.
- Выполнение - запускает выбранные задачи и их зависимости.
Это различие важно для производительности. Чтение файлов, преждевременное вычисление значений и запуск внешних процессов на фазе конфигурации замедляют каждый запуск и могут нарушить совместимость с configuration cache. Пользовательскую работу обычно следует оформлять как задачи с явно объявленными входами и выходами.
Структура проекта и репозитории
Типичный проект на Kotlin DSL содержит:
settings.gradle.kts- задаёт имя сборки, подключает модули и обычно настраивает репозитории плагинов и зависимостей;- корневой
build.gradle.kts- объявляет общие версии плагинов или минимальную конфигурацию корневого проекта; - модульный
build.gradle.kts- применяет плагины и настраивает один модуль приложения, библиотеки или функциональности; gradle/libs.versions.toml- необязательный каталог версий с псевдонимами зависимостей и плагинов;gradle.properties- общие свойства Gradle для проекта;gradle/wrapperиgradlew/gradlew.bat- фиксируют и запускают версию Gradle проекта.
Репозитории плагинов и библиотек решают разные задачи:
// settings.gradle.kts
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
include(":app", ":feature:profile", ":core:network")
По возможности держи объявления репозиториев в одном месте. Произвольные репозитории внутри модулей усложняют аудит и воспроизводимость разрешения зависимостей.
Модули и общая логика сборки
Каждый модуль применяет плагины, определяющие его роль, например com.android.application или com.android.library. Зависимости должны иметь понятное направление: функциональные модули могут зависеть от стабильных API core-модулей, а модуль приложения выступает финальной точкой композиции. Циклы обычно указывают на неверно распределённые обязанности или отсутствующие контракты.
Если множество модулей повторяют одни и те же настройки Android и Kotlin, вынеси их в convention plugins, обычно в подключённую сборку вроде build-logic. Такие плагины дают типобезопасные и тестируемые общие настройки, сохраняя модульные скрипты короткими. Избегай крупного блока subprojects {}, который неявно изменяет все модули.
Модуляризация не ускоряет сборку автоматически. Она помогает, только если границы допускают параллельную работу и предотвращают массовую перекомпиляцию; избыток модулей и связей повышает стоимость конфигурации и сопровождения.
Конфигурация Android Gradle Plugin
AGP предоставляет DSL-блок android:
android {
namespace = "com.example.app"
compileSdk = 36
defaultConfig {
applicationId = "com.example.app"
minSdk = 26
targetSdk = 36
versionCode = 1
versionName = "1.0"
}
}
compileSdkопределяет Android API, доступные при компиляции.minSdkзадаёт минимальный API level, на котором можно установить приложение.targetSdkобъявляет версию поведения Android, с которой приложение протестировано, и может включать новые изменения поведения платформы.namespaceиспользуется для сгенерированных классов вродеR, аapplicationIdприложения идентифицирует установленное и опубликованное приложение.
AGP также выполняет слияние манифестов, обработку ресурсов, генерацию кода, преобразование в DEX, подпись, lint-проверки, оптимизацию и упаковку. Версии Gradle, AGP, JDK, Kotlin и Android Studio имеют ограничения совместимости, поэтому обновлять их нужно вместе, сверяясь с официальными таблицами совместимости.
Конфигурации зависимостей
Зависимости должны находиться в модуле, который их использует:
dependencies {
implementation(libs.okhttp)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
ksp(libs.room.compiler)
}
Основные конфигурации:
implementation- зависимость доступна модулю, но не попадает в compile classpath его потребителей;api- зависимость видна потребителям, потому что её типы входят в публичный API модуля;compileOnly- нужна для компиляции, но отсутствует в runtime-пакете;runtimeOnly- нужна во время выполнения, но не компиляции;testImplementationиandroidTestImplementation- зависимости локальных и инструментальных тестов;kspилиkapt- зависимости, используемые соответствующим инструментом генерации кода.
По умолчанию выбирай implementation. Используй api, только если публичная сигнатура действительно содержит тип зависимости. Злоупотребление api ухудшает инкапсуляцию и может заставлять Gradle перекомпилировать больше зависимых модулей.
KSP работает непосредственно с Kotlin-символами и обычно предпочтительнее, если библиотека его поддерживает. Но миграцию нужно проверять: KSP-реализация есть не у каждого процессора аннотаций, а поведение сгенерированного кода может отличаться.
Каталоги версий
Каталог версий централизует псевдонимы и координаты:
[versions]
retrofit = "2.11.0"
[libraries]
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
Каталоги делают имена единообразными, но не контролируют границы зависимостей и не гарантируют единственную версию каждой библиотеки. Для более строгой воспроизводимости или защиты цепочки поставки применяй блокировку или проверку зависимостей.
Build types, product flavors, варианты и source sets
Build types обычно описывают этапы разработки, например debug и release. Product flavors описывают версии продукта: бренд, окружение или канал распространения. AGP создаёт вариант сборки из их комбинации.
android {
flavorDimensions += "environment"
productFlavors {
create("staging") { dimension = "environment" }
create("production") { dimension = "environment" }
}
buildTypes {
release { isMinifyEnabled = true }
}
}
Source sets предоставляют код и ресурсы для определённой области: src/main, src/debug, src/release, src/test, src/androidTest или каталога конкретного flavor/варианта. Дополнительные flavors умножают количество вариантов, задач, CI-работы и мест, где может скрываться разное поведение. Предпочитай runtime-конфигурацию, если отдельные артефакты действительно не нужны, и никогда не клади секреты в BuildConfig или ресурсы: значения из пакета можно извлечь.
APK, AAB и оптимизация
APK - устанавливаемый пакет с DEX-кодом, ресурсами, манифестом и, при необходимости, нативными библиотеками. Он удобен для локальной установки, тестирования и распространения вне магазинов.
Android App Bundle (AAB) - публикуемый артефакт, который нельзя напрямую установить командой adb install. Google Play создаёт из него APK для конкретного устройства, разделяя содержимое по ABI, плотности экрана, языку или динамическим модулям. Для анализа и тестирования APK, сгенерированных из bundle, используй bundletool.
Доставка через AAB может уменьшить размер загрузки, но не заменяет оптимизацию приложения. В release-сборках обычно следует применять R8 и удаление неиспользуемых ресурсов вместе с протестированными keep rules. Также удаляй ненужные зависимости и assets, ограничивай упакованные ABI и языки, когда это уместно, а dynamic features вводи только тогда, когда выгода от доставки оправдывает сложность.
Wrapper и воспроизводимая сборка
Разработчики и CI должны использовать добавленный в репозиторий Gradle Wrapper:
Wrapper загружает версию из gradle-wrapper.properties, поэтому глобальная установка Gradle не нужна. Храни wrapper-файлы в репозитории, обновляй их осознанно и проверяй checksum дистрибутива. Воспроизводимость также зависит от фиксированных зависимостей, контролируемых репозиториев, согласованных версий JDK/toolchain и отсутствия логики сборки, зависящей от окружения.
Производительность и диагностика сборки
Оптимизируй измеренные узкие места, а не предположения. Build scans, профилирование Gradle, вывод задач и Android Studio Build Analyzer помогают найти медленную конфигурацию, неинкрементальные процессоры, промахи кэша и неожиданно повторно выполняемые задачи.
Полезные принципы:
- явно объявляй входы и выходы задач, чтобы работали up-to-date checks и кэширование;
- используй build cache для повторного применения результатов задач, а configuration cache - результатов конфигурации: это разные механизмы;
- избегай ненужных
clean-сборок, динамических версий зависимостей и постоянно меняющихся сгенерированных файлов; - применяй ленивые Gradle API и не выполняй тяжёлую работу при конфигурации;
- сокращай широкие межмодульные зависимости и переходи с KAPT на KSP там, где он поддерживается;
- проверяй улучшения на репрезентативных локальных и CI-сборках.
Частые ошибки
- путать Gradle с AGP или считать Android Studio системой сборки;
- использовать несовместимые версии Gradle, AGP, JDK или Kotlin;
- раскрывать внутренние зависимости через
api; - создавать циклические или слишком широкие зависимости между модулями;
- дублировать или скрывать общую конфигурацию модулей;
- создавать слишком много flavors и вариант-специфичного поведения;
- помещать секреты в build-файлы или упакованные константы;
- создавать задачи без объявленных входов и выходов;
- запускать сетевые запросы, процессы или генерацию файлов во время конфигурации;
- считать кэш, переход на KSP или модуляризацию ускорением без измерений.
Главная мысль: здоровая Android-сборка явна, воспроизводима и предсказуема. Её граф модулей, входы, выходы и различия вариантов должно быть легко объяснить.