Речь пойдет о:
- Увеличении быстродействия
- Расширении BuildConfig
- Использовании переменных
- Выключении Crashlytics
- Уменьшении количества конфигураций ресурсов
1. Увеличиваем быстродействие
Время сборки напрямую влияет на скорость разработки. Тесты показывают, что каждая из версий, начиная с Gradle 2.0, становилась медленнее предыдущей. Однако затем разработчики исправились и хорошенько поработали над быстродействием в Gradle 2.4.
1. Поэтому первым делом следует убедиться, что вы используете актуальную версию Gradle 2.4+
sm:~ sm$ gradle -v
Gradle 2.4
2. Затем, удостоверившись, что вы пытаетесь ускорить свою рабочую машину, а не сервер CI, включить Gradle демон — это даст значительный прирост в скорости сборки.
Строки конфигурации стоит добавлять в файл ./%project%/gradle.properties, если вы хотите распространить конфигурацию на все проекты, то необходимо конфигурировать файл, лежащий в домашней папке вашего пользователя
~/.gradle/gradle.properties
org.gradle.daemon=true # включаем демон
3. После чего, проверив что модули вашего проекта не используют друг-друга как зависимости, тем самым создавая перекрёстные ссылки, можно смело включать режим параллельного выполнения, что также ускорит скорость сборки до ~30%.
org.gradle.parallel=true # включаем режим параллельного выполнения
Если в проекте используется много модулей, то стоит также включить режим конфигурации при необходимости:
org.gradle.configureondemand=true
Чем больше модулей в вашем проекте, тем большее вы заметите ускорение, на малом количестве модулей его может и не быть.
2. Расширяем BuildConfig
Как известно, файл конфигурации сборки (build.gradle) предоставляет возможность определить Product Flavors и Build Types, что даёт нам массу вариантов для разделения сборок по назначению. Например, “Сборка с тестовым сервером”, “Сборка с боевым сервером”, ”Сборка с логированием” и другие. Использование их для расширения BuildConfig (который генерируется каждый раз при сборке) даёт нам потрясающую гибкость. Например, удобное переключение между back-end-сервером, с которым работает наше приложение; включение/выключение определённого функционала – например, логи.
В build.gradle:
android {
...
buildTypes {
debug {
buildConfigField "String", "SERVER_PREFIX", "\"test.\""
}
release {
buildConfigField "String", "SERVER_PREFIX", "\"\""
}
}
}
В java коде:
// …
public final class NetworkConstants {
// …
public static final String SERVER_ADDRESS = "http://" + BuildConfig.SERVER_PREFIX + "server.com/";
// …
}
Build Type — конфигурация того, как приложение будет упаковано. У каждого приложения по умолчанию есть два Build Type – debug и release. Можно сделать и другие. Идеологически Build Type не предназначен для изменения приложения, только упаковки. Собственно это и является основным различием, которое выливается в различные наборы параметров, которые предоставляются для настройки Product Flavors и Build Types.
3. Используем переменные
Время не стоит на месте, а значит инструменты, библиотеки и Android имеют свойство обновляться. И если приложение развивается, то приходится открывать build.gradle и менять как минимум compileSdkVersion, buildToolsVersion, версии Android Support Library и Google Play Services. А если у нас в проекте используется много модулей или различные части библиотеки Google Play Services, это ведет к большому количеству мест изменений, и можно легко потерять время из-за опечатки в каком-то из файлов. Кроме того, возможно использование различных библиотек и инструментов в разных проектах, что плохо и может стать причиной проблем. Избежать подобной ситуации помогут gradle-переменные.
В самый верхний build.gradle добавляем
// …
ext.compileSdkProjectVersion= 23
ext.buildToolsProjectVersion= '23.0.1'
ext.supportLibraryVersion = '23.1.0'
ext.googlePlayVersion = '8.3.01’
В остальных ./%module%/build.gradle их можно будет использовать, выглядеть это будет примерно так:
android {
compileSdkVersion compileSdkProjectVersion
buildToolsVersion buildToolsProjectVersion
//…
}
dependencies {
compile "com.android.support:appcompat-v7:$supportLibraryVersion"
compile "com.google.android.gms:play-services-base:$googlePlayVersion"
compile "com.google.android.gms:play-services-maps:$googlePlayVersion"
compile "com.google.android.gms:play-services-location:$googlePlayVersion"
}
4. Выключаем Crashlytics
В большинстве случаев собирать аварийные завершения необходимо только в Release-сборках, которые мы выпускаем для пользователей/тестирования. Debug сборки разработчик использует для себя, и аварийные завершения будут видны ему в лог-файле, значит, дабы не засорять список реальных аварийных завершений у пользователей, необходимо выключить Crashlytics для Debug-сборок.
Задачу можно выполнить банальной проверкой типа сборки:
// App.java
// …
public final class App extends Application {
// ...
@Override
public void onCreate() {
// …
if ( !BuildConfig.DEBUG ) {
Fabric.with(this, new Crashlytics());
}
// …
}
// …
}
Но это не самое лучшее решение, т.к. плагин Fabric Gradle всё равно будет тратить время на генерацию и встраивание в ресурсы приложения уникального id сборки, чтобы Crashlytics back-end затем понял, какая сборка прислала данные. Поэтому применим более удобное решение, которое позволит ускорить время сборки debug-версии приложения.
В build.gradle:
android {
//…
buildTypes {
debug {
ext.enableCrashlytics = false
// …
}
}
// …
}
После этого debug-сборки не будут получать id, и процесс сборки ускорится, но следует учитывать, что если разработчик попытается инициализировать в такой сборке Crashlytics, то приложение упадёт с выводом:
com.crashlytics.android.core.CrashlyticsMissingDependencyException:
This app relies on Crashlytics. Please sign up for access at https://fabric.io/sign_up`
Т.е. обязательно оставьте проверку на тип сборки и используйте Crashlytics только для Release сборок или воспользуйтесь решением, приведённым в документации к Crashlytics на сайте Fabric:
// App.java
// …
public final class App extends Application {
// ...
@Override
public void onCreate() {
// …
//Создаём Crashlytics,выключенный для debug сборок
Crashlytics crashlyticsKit = new Crashlytics.Builder()
.core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
.build()
// Инициализируем Fabric с выключенным crashlytics.
Fabric.with(this, crashlyticsKit);
// …
}
// …
}
5. Уменьшаем количество конфигураций ресурсов
В разрабатываемых нами приложениях мы часто используем сторонние библиотеки, например, Android Support Library, Google Play Services и другие. Многие из библиотек поставляются с различными внутренними ресурсами, которые в наших приложениях абсолютно не нужны. Например, Google Play Services поставляется с переводом на языки, которые ваше приложение не поддерживает. Вероятно, вы также не захотите поддерживать mdpi или tvdpi-разрешение в своём приложении.
Благодаря Android Gradle Plugin мы можем установить языки и разрешения, которые используются в приложении, остальные будут исключены, что позволит уменьшить вес приложения.
// build.gradle
android {
defaultConfig {
resConfigs "en", "ru"
resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
}
}
При необходимости можно подойти более радикально и начать использовать multi-APK, тем более когда появился новый удобный механизм Splits.
Вместо заключения
Этим рубрику полезных советов по Gradle закончим, их ещё много, но, на мой взгляд, вышеперечисленные являются наиболее интересными. Наверняка у вас есть и свои, интересно будет увидеть их в комментариях :) На этом всё, спасибо за внимание!
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (17)
agent10
19.11.2015 19:28А указание resConfigs влияет только на последний шаг сборки, грубо говоря, только на размер apk? Т.е. разнообразный мёржинг ресурсов остаётся и на скорость сборки это не влияет?
scottKey
19.11.2015 20:13Да, всё верно. Ресурсы подготовит, смержит, а уже во время запаковки – выкинет.
agent10
19.11.2015 20:16Напрашивается вопрос — а зачем?) Я, конечно, детально со всеми этапами не знаком, но на лицо куча лишних действий)
scottKey
19.11.2015 20:29Этот аспект отражён в статье, только ради уменьшения размера apk.
agent10
19.11.2015 20:45Я понял, что ради уменьшения)
Я не понимаю, для чего готовить и мержить ресурсы, которые в результате будут выкинуты.
Например, у меня 10 языков, в каждом по 2 тысяче строк.
Но у меня не для всех flavors нужны все языки, где-то надо всего 2 языка.
Если бы ресурсы игнорировались на начальном этапе это могло бы немного, но повысить скорость сборки.archinamon
20.11.2015 18:53Кстати, он не должен мёржить ресурсы из других флаворов. Только из тех, вариант сборки которых выбран.
Но если говорить о произвольной выборке в пределах выбранного варианта, то Вы забываете про lint проверки. Некоторые проблемы с ресурсами выяляются только на этапе сборки. Это и отсутствие переводов строк, и коллизии/баги с 9патч. Представьте себе обработку 10-20 языков, по 2-5 тысяч записей в каждом, в фоне во время основной работы с проектом. Могу ошибаться, но по-моему логичнее незначительно увеличить время сборки, но убедиться в корректности всех ресурсов.
stepanp
19.11.2015 19:43Неужели в 2.4 он стал сносно по скорости работать?
scottKey
19.11.2015 20:18Вопрос сносности это всё же субъективный вопрос, есть объективные тесты с цифрами – разница очень существенная. Также могу сказать что разработчики Gradle обещали ускорить и следующие версии, т.е. 2.8/2.9 должны быть еще быстрее, но тестов я не проводил.
forceLain
20.11.2015 06:04Мне кажется, самая главная полезность — это осознание, что gradle — это не только система сборки, но и язык программирования (groovy), который в процессе билда дает возможность создавать и рушить вселенные, ну или еще что-нибудь полезное. У нас, к примеру, генерируется файл с историей версий (релизов) на основе гит тэгов.
Кусочек скриптаdef String[] tags = formatTagNames(getTagList()) def AppHistory appHistory = getAppHistory(tags) def String jsonHistory = new GsonBuilder().setPrettyPrinting().create().toJson(appHistory) new File("app_history.json").withWriter{ it << jsonHistory }
scottKey
20.11.2015 21:24Спасибо за полезный пример!
Стоит уточнить что Gradle использует Gradle DSL. Gradle DSL в свою очередь основан на Groovy, но расширяет его для более удобной работы с билд скриптами.
Если говорить о самом полезном в Gradle, думаю, это декларативность. Декларативность позволяет создавать и рушить вселенные очень удобно и легко, не задумываясь о очень многих вещах.
archinamon
В предыдущем проекте пришёл к выводу, что получается очень удобно если просто вынести все метрики и аналитики в отдельный флавор. Таким образом, в дев-версию сборки, где не нужен крашлитикс и аналитика, соответствующие зависимости даже не попадут в classpath.
Для этого:
scottKey
Тоже хорошое решение, но если вариантов которым будет необходим крашлитикс и аналитика несколько, то прийдется дублировать.
archinamon
Не обязательно. Можно сделать через flavor dimensions. Например, есть дименшен device: phone, tablet, wear; и есть дименшен environment: production, develop, marketing. На выходе получаем device.count * environment.count * buildType.count вариантов (18 вариантов сборки).
scottKey
Да, всё верно. И насколько я понимаю вы используете как phoneProductionDebug, так и phoneProductionRelease варианты сборки?
archinamon
Оба варианта используются, в итоге. Релиз берёт CI-машина, а дебаг обычно собирается только на локальных машинах, если нужен.
scottKey
Тогда у вас хорошее решение, спасибо что поделились!