Site icon AppTractor

Как я сократил время сборки Gradle на 50%

Разработчикам Android знакома проблема длительного времени сборки. По мере роста сложности проектов, работа Gradle может быстро превратиться в разочарование и снижение производительности. Хотя Kotlin DSL привносит в скрипты сборки современный синтаксис и безопасность типов, он также может привнести значительные издержки производительности по сравнению со своим более старым аналогом Groovy.

В этой статье мы рассмотрим практические стратегии повышения производительности сборки Gradle в Android-проектах, использующих Kotlin. Мы рассмотрим полезные конфигурации gradle.properties, обсудим, как обновление версий Gradle, Kotlin и Java может дать ощутимую разницу, а также подробно рассмотрим компромиссы между скриптами сборки на Groovy и Kotlin — да, Groovy все еще имеет небольшое преимущество, когда речь идет о скорости.

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

Создайте бенчмарки

Прежде чем приступать к каким-либо изменениям, следует установить четкую базовую линию. Обязательно запустите несколько бенчмарков на вашей текущем проекте, чтобы вы могли точно оценить влияние каждого изменения. Отслеживайте как чистые сборки (после очистки кэша Gradle), так и инкрементные сборки, чтобы получить полную картину. И не просто запускайте каждую сборку один раз — по возможности запускайте их несколько раз. Такие вещи, как фоновые процессы, индексация и даже тепловое состояние вашего ноутбука, могут повлиять на результаты больше, чем вы ожидаете. Записывайте свои наблюдения по ходу работы. Это может показаться немного утомительным, но это поможет вам принимать обоснованные решения, а не полагаться на догадки.

Change/Config Type of Build Run # Build Time (s) Notes
Baseline (no changes) Clean Build 1 73.2 Initial reference
Clean Build 2 71.8 Slight variation
Incremental Build 1 15.4 No code changes
Added org.gradle.daemon=true Clean Build 1 72.1 Minimal impact
Incremental Build 1 12.8 Slightly faster
Switched to Groovy DSL Clean Build 1 69.5 Noticeable improvement
Incremental Build 1 11.3 Faster than Kotlin DSL
Upgraded Gradle to 8.5 Clean Build 1 66.2 Gradle upgrade helped
Clean Build 2 65.9 Consistent results
Incremental Build 1 10.5 Best result so far

Вы можете настроить столбцы в зависимости от того, насколько глубоко вы хотите погрузиться в оптимизацию (например, добавить использование оперативной памяти, нагрузку процессора или конкретные характеристики машины).

Обновите все

Прежде чем настраивать свойства или переключать DSL, убедитесь, что вы работаете с последними стабильными версиями ваших инструментов. Gradle, Kotlin, Android Gradle Plugin (AGP) и даже сама Java регулярно получают обновления, которые включают в себя не только новые функции, но и значительные улучшения производительности под капотом.

На момент написания статьи вот некоторые версии, на которые стоит обратить внимание:

Обновление этих компонентов может значительно сократить время сборки само по себе. Не забудьте повторно прогнать свои бенчмарки после каждого обновления версии, чтобы увидеть эффект. Иногда даже незначительный патч может сэкономить несколько драгоценных секунд.

Не забудьте также проверить версию вашей IDE — обновления Android Studio часто улучшают производительность кэширования и индексирования сборок, что может улучшить ваш рабочий процесс, а вы даже и не заметите этого.

Оптимизируйте gradle.properties

Один из самых простых способов повысить производительность Gradle — оптимизировать файл gradle.properties. Ниже перечислены некоторые важные свойства, которые могут иметь значение, а также несколько дополнительных, которые могут оказаться полезными для ускорения работы.

Настройте память JVM

org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true

Включите кэширование Gradle

org.gradle.caching=true

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

Параллельные сборки

org.gradle.parallel=true

Это свойство позволяет параллельно выполнять независимые задачи. Оно наиболее полезно для многомодульных проектов, где задачи в разных модулях могут выполняться одновременно. Обратите внимание: это свойство следует использовать в несвязанных (decoupled) проектах — тех, которые имеют минимальную взаимозависимость. Для тесно связанных проектов это может вызвать проблемы.

Конфигурирование по требованию

org.gradle.configureondemand=true

Для многомодульных проектов это указывает Gradle настраивать только те проекты, которые необходимы для текущей сборки. Это уменьшает накладные расходы на конфигурацию, когда нужно собрать только определенное подмножество модулей, что повышает скорость работы с большими проектами.

Используйте демон Gradle

org.gradle.daemon=true

Включив демон Gradle, вы сохраняете процесс запущенным между сборками, избегая времени запуска, необходимого для каждой сборки. Это может значительно сократить время сборки, особенно для инкрементных сборок. Gradle будет автоматически управлять жизненным циклом демона, поэтому никакой дополнительной настройки, кроме этого свойства, не требуется.

Следите за чистотой логов

org.gradle.logging.level=info

Установка уровня ведения логов на info гарантирует, что Gradle будет записывать в журнал только важную информацию во время сборки. Это позволяет сократить время, затрачиваемое на обработку подробных логов, и ускорить процесс сборки. Вы можете установить уровень quiet, если вам нужен минимальный вывод.

Оптимизируйте разрешение зависимостей

Разрешение зависимостей часто может быть скрытым узким местом в производительности сборки. Gradle необходимо разрешить и загрузить зависимости из удаленных репозиториев, и этот процесс может занять время — особенно если вы работаете с большим количеством сторонних библиотек. Вот несколько ключевых стратегий, позволяющих оптимизировать работу Gradle с зависимостями.

Избегайте ненужных и неиспользуемых зависимостей

Управление сторонними библиотеками и их транзитивными зависимостями очень важно, но это может привести к ненужным накладным расходам. Неиспользуемые зависимости могут накапливаться со временем, особенно во время рефакторинга, и увеличивают как стоимость обслуживания, так и время сборки.

Лучшие практики:

Оптимизируйте порядок репозиториев

Gradle разрешает зависимости, перебирая репозитории в порядке их объявления. Если у вас несколько репозиториев, Gradle будет искать в них по очереди, что потенциально увеличивает количество сетевых запросов.

Лучшая практика:

repositories {
    mavenCentral()   // Most dependencies go here, so put it first
    google()         // Second, as Android-related dependencies often live here
    maven { url = uri("https://my.custom.repo") }  // Custom repositories last
}

Минимизируйте количество репозиториев

Чем больше репозиториев вы объявляете, тем больше времени требуется Gradle для их поиска. Сокращая количество репозиториев, вы уменьшаете накладные расходы.

Лучшая практика:

Это минимизирует количество сетевых вызовов, которые приходится делать Gradle.

Минимизируйте динамические и Snapshot версии

Динамические версии (например, «2.+» или «latest.integration«) и снепшоты зависимостей заставляют Gradle проверять удаленные репозитории каждый раз, когда вы собираете сборку, чтобы получить более новые версии. Это может замедлить работу, так как Gradle приходится постоянно проверять, доступны ли новые версии.

Лучшая практика:

# Cache dynamic versions for 24 hours (default)
org.gradle.cache.dynamicVersionsFor=24h
# Cache changing modules for 48 hours
org.gradle.cache.changingModulesFor=48h

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

Ищите динамические версии с помощью сканирования

Если вы подозреваете, что динамические версии замедляют сборку, но не уверены, где они используются, Gradle Build Scans поможет их выявить.

Как использовать: запустите сканирование сборки и найдите все зависимости, использующие динамические версии. Сканирование сборки обеспечивает визуальное представление вашего процесса сборки и позволяет выявить такие проблемы, как эта.

gradle build --scan

После завершения сканирования перейдите по указанной ссылке, чтобы проанализировать вашу сборку. Вы сможете точно определить, какие зависимости используют динамические или снепшот версии.

Groovy vs. Kotlin DSL: компромисс в скорости

Когда речь заходит о скриптах сборки, Groovy все еще имеет небольшое преимущество в производительности перед Kotlin DSL. Хотя Kotlin безопасен с точки зрения типов, имеет поддержку IDE и лучшие инструменты для рефакторинга, Groovy немного быстрее выполняется в сборках Gradle благодаря своей более простой, динамически типизированной природе.

Тем не менее, Kotlin идеально подходит для больших проектов, где безопасность, ясность и сопровождаемость важнее, чем скорость выполнения. Если вы работаете над небольшим проектом или вам нужно выжать каждую секунду времени сборки, использование Groovy может дать вам небольшое преимущество. Но для большинства Android-проектов преимущества Kotlin DSL — например, более эффективная проверка ошибок, поддержка IDE и автодополнение — перевешивают незначительные затраты на производительность.

Таким образом, хотя Groovy и быстрее, Kotlin DSL идеально подходит для тех, кто ставит во главу угла долгосрочное качество кода и инструментарий.

Заключение

Оптимизация времени сборки Gradle может показаться сложной задачей, но, как мы убедились, внесение небольших, целенаправленных изменений может привести к значительным улучшениям. Будь то настройка gradle.properties, обновление зависимостей или настройка параллельного выполнения, каждый шаг может привести к более быстрой и плавной разработке.

На самом деле, в одном из недавних проектов мне удалось сократить время сборки с помощью Gradle на 50%, уменьшив время сборки с 9 минут до всего лишь 4:30. Как? Добавив несколько ключевых оптимизаций в файл gradle.properties:

org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.configureondemand=true

Эти настройки помогли мне максимально использовать мощные возможности Gradle по кэшированию, параллелизму и настройке JVM, что способствовало значительному росту производительности.

Если вы ищете идеи, как повысить производительность сборки, обязательно ознакомьтесь с этим замечательным проектом Pokedex, который демонстрирует некоторые современные методы разработки Android и стратегии оптимизации производительности. Это фантастическое руководство для тех, кто хочет повысить скорость своих Android-сборок.

Для получения еще более продвинутых советов и опций не забудьте посетить официальное руководство по производительности Gradle здесь.

Сочетая советы из этой статьи с некоторыми инструментами и настройками, которые мы обсудили, вы будете на пути к более быстрым сборкам и более эффективным циклам разработки. Помните, что каждый проект индивидуален, поэтому не бойтесь экспериментировать и смотреть, что лучше всего подходит именно для вашей системы!

Источник

Exit mobile version