Irbisa · Шпаргалка

Gradle и модуляризация

Gradle KTS, каталоги версий, convention-плагины, KSP, варианты сборки, configuration cache, модули

Senior-разработчик · 20 вопросов

  1. 01

    Из каких Gradle-файлов состоит Android-проект и за что отвечает каждый?

    settings.gradle.kts объявляет, какие модули есть в проекте и откуда скачиваются плагины и библиотеки, build.gradle.kts каждого модуля настраивает этот модуль, а Gradle wrapper закрепляет одну версию Gradle для всех машин.

    // settings.gradle.ktspluginManagement {    repositories {        google()        mavenCentral()        gradlePluginPortal()…
  2. 02

    Что такое Android Lint и как заставить его ронять сборку, а не генерировать отчёт, который никто не читает?

    Android Lint представляет собой статический анализатор, встроенный в AGP: он проверяет код, ресурсы и манифест на реальные баги (вызов API выше minSdk, отсутствующее разрешение, неиспользуемый ресурс).

    // app/build.gradle.ktsandroid {    lint {        abortOnError = true                   // the default: errors fail the task        warningsAsErrors = true        baseline = file("lint-baseline.xml")  // report only issues newer than this file…
  3. 03

    Что такое multidex, зачем он был нужен и нужен ли он сейчас?

    Один файл classes.dex может ссылаться максимум на 65 536 методов, поэтому большие приложения делятся на classes2.dex, classes3.dex и так далее.

  4. 04

    Что выведет ./gradlew hello для этого build-скрипта и что выведет ./gradlew help?

    // build.gradle.ktsprintln("1: script body") tasks.register("hello") {    println("2: configuring hello")    doLast { println("3: running hello") }} tasks.register("cleanup") {    println("4: configuring cleanup")} println("5: end of script")

    ./gradlew hello выведет 1, 5, 2, 3, а ./gradlew help выведет только 1 и 5.

  5. 05

    В чём разница между implementation, api, compileOnly и runtimeOnly?

    Они определяют, в какие classpath попадает зависимость.

    // core/network/build.gradle.ktsdependencies {    api(project(":core:model"))         // `User` from :core:model is in this module's public API    implementation(libs.retrofit)       // used inside, never exposed    compileOnly(libs.javax.annotation)  // annotations only, not packaged    runtimeOnly(libs.slf4j.android)     // a backend nobody imports…
  6. 06

    Что такое version catalog и зачем рядом с ним всё равно нужен BOM?

    Version catalog (gradle/libs.versions.toml) хранит общий список библиотек, плагинов и версий с типобезопасными аксессорами вроде libs.retrofit.

    // gradle/libs.versions.toml// [versions]// composeBom = "2025.09.00"// retrofit = "3.0.0"//// [libraries]…
  7. 07

    В чём разница между build types и product flavors и как собрать staging-приложение со своим API URL и иконкой рядом с продакшеном?

    Build types описывают, как собирается приложение (debuggable, подпись, минификация), product flavors описывают, какое приложение собирается (free или paid, staging или prod), а build variant получается их комбинацией.

    android {    namespace = "com.example.app"    buildFeatures { buildConfig = true }   // off by default since AGP 8     defaultConfig {        applicationId = "com.example.app"…
  8. 08

    Сколько build variants даёт эта конфигурация и какие source sets компилирует freePlayDebug?

    android {    flavorDimensions += listOf("tier", "store")    productFlavors {        create("free") { dimension = "tier" }        create("paid") { dimension = "tier" }        create("play") { dimension = "store" }        create("huawei") { dimension = "store" }    }    buildTypes {        getByName("debug")        getByName("release")        create("qa") { initWith(getByName("release")) }    }}

    Двенадцать: 2 tier × 2 store × 3 build types, у каждого свой набор задач.

  9. 09

    Что происходит между ./gradlew assembleRelease и подписанным APK?

    Исходники компилируются в .class, D8 (или R8 при isMinifyEnabled = true) делает из них classes.dex, AAPT2 компилирует ресурсы и объединённый манифест, а результат пакуется в zip, выравнивается и подписывается.

  10. 10

    В чём разница между kapt и KSP и что даёт миграция?

    kapt запускает Java annotation processors, сначала генерируя Java-заглушки (stubs) для каждого Kotlin-файла и компилируя их javac; KSP работает как Kotlin-API, которое читает символы компилятора напрямую.

    // before://   plugins { id("org.jetbrains.kotlin.kapt") }//   kapt(libs.room.compiler) plugins {    alias(libs.plugins.android.library)…
  11. 11

    Как Gradle разрешает конфликты версий зависимостей и как разобраться с ошибкой «Duplicate class»?

    Когда два пути в графе запрашивают разные версии одного модуля, Gradle выбирает самую высокую, в отличие от правила nearest-wins в Maven.

    // Who asked for it, and why did this version win?//   ./gradlew :app:dependencyInsight --configuration releaseRuntimeClasspath --dependency okio dependencies {    // one family, one version    implementation(platform(libs.okhttp.bom))…
  12. 12

    Что делает Java toolchain в Android-сборке и что означает «Inconsistent JVM-target compatibility»?

    Toolchain закрепляет JDK, которым компилируется код, независимо от того, какой JDK запускает Gradle.

    kotlin { jvmToolchain(17) } android {    compileOptions {        // both must name the same level        sourceCompatibility = JavaVersion.VERSION_17…
  13. 13

    Почему любое изменение в buildSrc переконфигурирует весь проект и что меняется, когда convention-плагины живут в included build?

    buildSrc лежит на classpath каждого build-скрипта, поэтому любое изменение в нём делает устаревшими все скомпилированные скрипты и вызывает полную переконфигурацию.

    // settings.gradle.ktspluginManagement {    includeBuild("build-logic")   // a separate build, not a project dependency} // build-logic/convention/build.gradle.kts…
  14. 14

    Эта задача кодогенерации перезапускается на каждой сборке и падает при включённом configuration cache: что с ней не так и как это исправить?

    tasks.register("generateConfig") {    doLast {        val dir = project.layout.buildDirectory.dir("generated/config").get().asFile        dir.mkdirs()        val url = System.getenv("API_URL") ?: "https://api.example.com"        File(dir, "Config.kt").writeText("package com.example\n\nconst val API_URL = \"$url\"\n")    }} tasks.named("preBuild") { dependsOn("generateConfig") }

    Задача не объявляет ни входов, ни выходов, поэтому Gradle никогда не может счесть её актуальной (up to date), а её действие обращается к project во время выполнения, что configuration cache запрещает.

  15. 15

    В чём разница между build cache и configuration cache и что мешает каждому из них срабатывать?

    Configuration cache хранит готовый граф задач и пропускает всю фазу конфигурации; build cache хранит выходы задач по ключу из их входов и пропускает сами задачи.

  16. 16

    Gradle-сборка медленная: как выяснить почему и какие изменения обычно дают самый большой выигрыш?

    Сначала измерить, потом менять: build scan или Build Analyzer в Android Studio на реалистичной инкрементальной сборке покажут, куда уходит время (на конфигурацию, на несколько медленных задач или на длинную цепочку модулей, ждущих друг друга).

    # gradle.properties - change one thing at a time and measure againorg.gradle.caching=trueorg.gradle.configuration-cache=trueorg.gradle.parallel=trueorg.gradle.jvmargs=-Xmx6g -XX:+UseParallelGCkotlin.daemon.jvmargs=-Xmx3g…
  17. 17

    Когда разделение модуля на :api и :impl ускоряет сборку, а когда это лишний бойлерплейт?

    Оно окупается, когда сервис используют многие модули, а его реализация часто меняется: потребители компилируются против маленького стабильного модуля :api, и правка реализации перекомпилирует только :impl и :app.

  18. 18

    Как сделать разрешение зависимостей воспроизводимым, чтобы тот же коммит через месяц получил те же артефакты?

    Запретить плавающие версии, зафиксировать итоговый граф и проверять контрольные суммы артефактов.

  19. 19

    Что нужно сделать правильно при публикации Android-библиотеки в виде AAR в Maven-репозиторий?

    Кроме подключения maven-publish: опубликовать явный вариант, чтобы потребители получили правильный артефакт и метаданные, положить в AAR правила R8 для потребителей и защитить публичный API, чтобы узнать о ломающем изменении раньше потребителей.

    // core/network/build.gradle.ktsplugins {    alias(libs.plugins.android.library)    `maven-publish`}…
  20. 20

    Как dynamic feature module меняет граф зависимостей и как базовое приложение открывает его экран?

    Стрелка разворачивается: dynamic feature зависит от :app, а не наоборот, поэтому база не может импортировать ни один класс фичи.

    // app/build.gradle.ktsandroid {    dynamicFeatures += setOf(":feature:scan")} // feature/scan/build.gradle.kts - note the direction of the dependency…