Перейти к содержанию

Gradle & Build System

Gradle - инструмент сборки Android-проектов. Он разрешает зависимости, настраивает модули, выбирает и выполняет задачи, а затем создаёт артефакты для тестирования или распространения. Android Gradle Plugin (AGP) добавляет Android-специфичные сущности: манифесты, ресурсы, уровни SDK, варианты сборки, подпись, lint и упаковку APK/AAB.

Gradle, AGP и жизненный цикл сборки

Gradle - универсальный движок сборки, а AGP - плагин, который учит его собирать Android-приложения и библиотеки. Плагины Kotlin, KSP, сериализации и другие плагины добавляют собственные задачи и настройки.

Запуск Gradle состоит из трёх основных фаз:

  1. Инициализация - Gradle читает файл настроек и определяет проекты, участвующие в сборке.
  2. Конфигурация - выполняет логику сборки, применяет плагины и создаёт или настраивает граф задач.
  3. Выполнение - запускает выбранные задачи и их зависимости.

Это различие важно для производительности. Чтение файлов, преждевременное вычисление значений и запуск внешних процессов на фазе конфигурации замедляют каждый запуск и могут нарушить совместимость с 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" }
dependencies {
    implementation(libs.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:

./gradlew assembleDebug
./gradlew test

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-сборка явна, воспроизводима и предсказуема. Её граф модулей, входы, выходы и различия вариантов должно быть легко объяснить.

Связанные темы