Привет, Хабр! На связи «Исходный Код». Сегодня разбираем инструмент, который мы все запускаем по сто раз на дню, но часто воспринимаем как черный ящик. Без него мы бы до сих пор собирали проекты руками, писали скрипты на bash и страдали от вечного «у меня собирается, а у тебя нет».

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

Что такое Gradle и как он мыслит

Это не просто скрипт, а система сборки, которая автоматизирует компиляцию, тесты, упаковку и публикацию. Ее движок держится на трех концепциях:

  • DAG (Directed Acyclic Graph - направленный ациклический граф): Перед стартом Gradle строит граф всех задач и их зависимостей. Это нужно, чтобы понимать точный порядок запуска и параллелить то, что можно.

  • Инкрементальная сборка: не делаем работу, которая уже сделана.

  • Кэш: локальный и удаленный.

Вся работа крутится вокруг трех понятий: Project (модуль сборки - корневой или вложенный, у каждого может быть свой build.gradle), Task (минимальная единица работы, вроде compileKotlin) и Plugin (код, расширяющий возможности Gradle).

Примеры структуры проектов, build logic или код GreetingPlugin. Ссылка на код: https://github.com/Codesrc-public-ru/Examples/blob/main/Gradle/libs.versions.toml
Примеры структуры проектов, build logic или код GreetingPlugin. Ссылка на код: https://github.com/Codesrc-public-ru/Examples/blob/main/Gradle/libs.versions.toml

Основные понятия Gradle

Project

  • Один build.gradle(.kts) = один проект

  • Multi-module = несколько проектов

Task

  • Минимальная единица работы

    • Примеры:

    • compileKotlin

    • test

    • assembleDebug

Plugin

  • Код, который расширяет Gradle

  • Плагин = код, который:

    • регистрирует задачи

    • добавляет DSL

    • вмешивается в lifecycle сборки

Ссылка на код: https://github.com/Codesrc-public-ru/Examples/blob/main/Gradle/GreetingPlugin.kt
  • Добавляет задачи, DSL, логику

  • Бывают внешние и локальные

    • Локальный через buildSrc

      ✔ Работает

      ❌Всегда конфигурируется

      ❌Тормозит sync

      ❌Плохо масштабируется

      ❌Один на весь проект

Ссылка на код: https://github.com/Codesrc-public-ru/Examples/blob/main/Gradle/project-structure-buildSrc.txt
  • Локальный через composite build

  • Gradle собирает несколько независимых билдов как один.

  • Подключение - В корневом settings.gradle: includeBuild("build-logic")

Ссылка на код: https://github.com/Codesrc-public-ru/Examples/blob/main/Gradle/build.gradle
Ссылка на код: https://github.com/Codesrc-public-ru/Examples/blob/main/Gradle/ConventionRegister.gradle.kts
Ссылка на код: https://github.com/Codesrc-public-ru/Examples/blob/main/Gradle/project-structure-build-logic.txt

Примеры:

  • com.google.devtools.ksp

    • Kotlin Symbol Processing

    • замена KAPT

    • быстрее

    • меньше магии

  • Kotlin

    • подключает Kotlin

    • настраивает компиляцию

    • связывает Kotlin ↔ Android

  • com.android.application

    • Android Gradle Plugin

    • android {} DSL

    • build variants

    • AAPT, DEX, packaging

    • Самый тяжелый плагин

  • Composite build

Dependency

  • Внешние библиотеки или другие модули

Жизненный цикл сборки (то, что под капотом)

Если вы понимаете эти три фазы, вы уже решаете 80% проблем со скоростью.

  • Initialization: чтение settings.gradle и понимание, какие модули участвуют в сборке.

  • Configuration: выполняются скрипты build.gradle, создаются объекты задач и строится граф.

  • Execution: запускаются только нужные задачи в правильном порядке.

Почему фаза Configuration - главная боль

Самый частый фейл: пихать тяжелую логику в конфигурацию. Чего категорически нельзя делать на этом этапе: читать файлы, ходить в сеть, запускать shell-скрипты или делать сложные вычисления.

Итог такого кода: мучительно медленный sync в IDE, тормозящий билд, и ненависть к инструменту.

Ссылка на код: https://github.com/Codesrc-public-ru/Examples/blob/main/Gradle/build-utils.gradle
Примеры плохого кода с val config = file(...).readText() и хорошего lazy API. Ссылка на код: https://github.com/Codesrc-public-ru/Examples/blob/main/Gradle/AndroidVariantsMigration.kt
Примеры плохого кода с val config = file(...).readText() и хорошего lazy API. Ссылка на код: https://github.com/Codesrc-public-ru/Examples/blob/main/Gradle/AndroidVariantsMigration.kt

Как правильно: конфигурация должна быть декларативной. Используйте lazy API: забудьте про tasks.create и используйте tasks.register. Так задача не создается сразу, а появляется только когда она реально нужна. Это делает конфигурацию дешевой.

Инкрементальная сборка: ловим статус UP-TO-DATE

Инкрементальная задача обрабатывает только изменения. Gradle жестко сравнивает три вещи:

  • Inputs (входы): файлы, параметры,

  • Outputs (выходы): сгенерированные файлы и артефакты,

  • Implementation: classpath задачи (код плагина и его зависимости).

Если ничего не поменялось, задача получает статус UP-TO-DATE или FROM-CACHE и не выполняется. Проверьте себя через gradlew assembleDebug --info. Если у вас везде EXECUTED - значит, вы сломали инкрементальность.

Особенности сборки Android (и почему она тормозит)

Android Gradle Plugin (AGP) - это огромная надстройка, которая генерирует сотни задач, управляя ресурсами, DEX, вариантами сборки (build types, flavors).

  • Sync vs Build: Sync в Android Studio - это конфигурация и построение модели проекта для IDE. Это дорогая операция. На больших проектах Sync может идти дольше самого билда.

  • KAPT - тихий убийца: обработка аннотаций через KAPT убивает скорость. Переходите на KSP - он быстрее KAPT в 2 раза и больше. На крупных проектах это экономит минуты на каждой сборке.

  • Ресурсы дороже кода: изменение одного XML часто обходится дороже изменения Kotlin-файла, так как AAPT2 триггерит десятки связанных задач.

Золотые правила (или как спать спокойно)

Держите чек-лист для наведения порядка в билдах:

  • Всегда используйте Gradle Wrapper (gradlew). Гарантирует нужную версию у всех в команде и на CI.

  • Обновляйте правильно. Сначала AGP, потом Gradle - они жестко связаны.

  • Только lazy API. Регистрируем задачи через tasks.register, используем Provider.

  • Ограничьте I/O в конфигурации. Любое чтение файлов и запросы - только в action-блоках задачи (doFirst/doLast) или через Provider API.

  • Включите Build Cache. Он кэширует результаты детерминированных задач.

  • Снижайте количество плагинов. Каждый лишний плагин может добавить десятки задач и сломать кэш.

org.gradle.jvmargs=-Xmx4g

org.gradle.parallel=true

org.gradle.caching=true

org.gradle.configuration-cache=true

Оптимизация JVM и параметры Gradle

Выводы

Gradle - инструмент мощный, но он не прощает небрежности. Большинство проблем со скоростью в Android-разработке связаны не с самой системой, а с разросшимся масштабом проекта и кривой настройкой. Если перестать запихивать бизнес-логику в конфигурацию, перейти на KSP и настроить кэш - время сборки можно сократить кратно.

Внедрили эти правила у себя - стало заметно стабильнее. Переходите к практике и оптимизируйте ваши build.gradle уже сегодня! Делитесь в комментариях своими лайфхаками: что больше всего тормозило ваши сборки и как вы это починили?

Автор статьи: Игорь Дмитриев, senior Android-разработчик в “Исходном Коде”.

Комментарии (0)