
Nobody likes to wait endlessly for their projects to build. Here are some methods that we at Tooploox employ to spend more time creating great apps, and less time building them.
Use the newest Gradle distribution
Gradle keeps getting faster and faster with each release, and it rarely makes sense to use older version. Android Studio keeps generating gradle wrapper that is ages behind – Android Studio 2.3 Canary 2 still uses version 2.14.1 – already few months old as of writing this article. Unless it breaks your build – and it shouldn’t – update.
Project-wide
To update the gradle wrapper for your project, simply run wrapper
task:
for example
This will download specified gradle distribution into wrapper files. Also, consider periodically removing old versions leftovers from `.gradle` folder inside your project.
You can update gradle wrapper from Android Studio as well – open
ProjectStructure
dialog and updateGradle version
field inProject
tab.wrapper
task is preferred, though, since it also updates the wrapperjar
file as well asgradlew
scripts.
System-wide
Despite gradle wrapper being the preferred method of running gradle builds, for non-production builds consider using locally installed gradle distribution. This way you can benefit from the latest performance improvements while not having to modify (possibly someone else’s) project’s gradle wrapper. To install gradle locally, refer to official website, or use packet manager like Macports or Homebrew. And update it periodically!
Configure Gradle properly
Gradle is extremely complex build system and as such it can be configured extensively. We’ll cover only some options – the ones that are the most important from the performance standpoint.
Project-wide
Project-wide settings for your gradle build are kept in gradle.properties
file in your project’s root folder.
System-wide
System wide settings can be found in .gradle
folder in your HOME directory – ~/.gradle/gradle.properties
. On Windows look in C:Usersusername.gradlegradle.properties
).
First Gradle reads all project-wide options, followed by system-wide options. If an option is specified twice, the last one wins. This essentially means you always override project’s properties by your system-wide options.
Options
org.gradle.daemon
– should be set totrue
to take advantage of Gradle daemon. Long story short, daemon means reusing things in subsequent buildsorg.gradle.jvmargs
– specifies jvmargs used for daemon process. You want to set high enough memory limit to fix _dex_ in Gradle process, so minimum configuration here is-Xmx2g
, while 3-4 gigabytes won’t hurt eitherorg.gradle.configureondemand
– slight optimisation that, if set totrue
, configures only needed modules of the project. Useful in large, multi-module projectsorg.gradle.parallel
– allows Gradle to build modules in parallel. Only useful, if your modules don’t depend on each other
Sample, efficient gradle.properties
file for a 16GB machine looks like this:
Remember that
-Xmx
argument should be set to at least yourdexOptions.javaMaxHeapSize
option plus some memory for Gradle build itself. If you don’t specify dex heap size manually, 1GB is used by default.
Convert your pngs to webps
Android has partial (no transparency and no lossless encoding) support for webp
format since Android 4.0 (API 14), and full support came with API 18. If you’re lucky enough to be able to use it, Android Studio 2.3 offers quick fix that converts your existing png
s into webp
s – simply right-click image (or a folder, even res
) and choose Convert to WebP...
. This can shave off several megabytes, from your APK, which may also speed up sending your application to the device.
One thing to keep in mind is that webp
images aren’t supported as launcher icons!
Edited: processing or merging resources is not, in fact, related to dexing. Previously stated significant speedup from using
webp
instead ofpng
was the result ofaapt
not crunching images anymore. Thanks to Jake Wharton for pointing this out!
Do not crunch your pngs
If you’re not lucky enough to use webp
s, you can still speed up your build by disabling png
crunching. From Android documentation on drawables:
Image resources placed in res/drawable/ may be automatically optimized with lossless image compression by the aapt tool during the build process. For example, a true-color PNG that does not require more than 256 colors may be converted to an 8-bit PNG with a color palette.
This, obviously, takes time. You can skip this optimisation by providing proper option to aapt
:
In one of ours png
-heavy projects, this alone reduced build times from 4 minutes to 40 seconds. You may want to reenable this for production build (or disable only for development) though, as crunching is, after all, an optimisation.
Use dex in process
Fortunately this is now enabled by default, but to reiterate – make sure your gradle daemon has enough memory allocated (at least 1GB)!
If at some point your build suddenly slows down, it might be the sign that your heap size is not big enough to fit dex in process anymore. You can then increase dex memory size in your module’s build.gradle
file:
Remember that afterwards you need to increase heap size for Gradle as well!
Increasing java max heap size for dex process should be used sparingly, as it’s rarely needed.
Pre-dex libraries
Simply setting preDexLibraries
to true
may speed up incremental builds.
It can slow down clean builds though, so it’s worth disabling this option for builds in your CI environment.
Set minSdkVersion to 21+ when using Multidex
If you use multidex, you can greatly improve your build times if your minSdkVersion
is set to 21+. You can either create a special dev
flavor for you app that overrides minSdkVersion
, or have the version computed dynamically based on a gradle property:
And then compile with, for example, ./gradlew installDebug -PminSdk=23
. You can set -PminSdk=23
option in Build->Compiler->Command line options
in your Android Studio as well, to use this option for builds triggered from your IDE.
Remember that Android Studio uses specified command line options also for generating signed APK. If you’re preparing a release APK, you might want to use command line, to be sure you haven’t built yours with too high minimum SDK version.
Avoid computations in Gradle configuration phase
When you trigger a build, at first your gradle projects are configured. This (more or less) runs the parts of your gradle build scripts that aren’t tasks. There’s a popular trick of having git sha in your BuildConfig
class:
The problem here is that every time you run a task, getGitHash
will be run. While this particular action may not be very inefficient in itself, lots of such small actions pile up. Consider only running these when building certain flavours, like staging builds, or only on CI environment.
This is also important when applying external plugins. You most likely use Crashlytics in your app – for development purposes it is a good idea to disable not only crashes reporting (directly in your application), but also Fabric plugin itself:
Limit packaged resources configurations
In projects with multiple configurations (like translations) you can save some time by only packaging resources for single language or screen size:
You can mix configurations, so resConfigs "en", "xhdpi"
would be also valid, as well as for example resConfigs "en", "fr", "xxhdpi"
.
Use JCenter
Long story short, there is now no reason to usemavenCentral()
over jcenter()
in your repositories
blocks. JCenter
is bigger, more secure and – even if ever so slightly – faster.
Use specific dependencies versions
Aside from being bad practice, using +
in dependencies versions forces gradle to check for newest dependency version each time you build your project. Always use specific library version, like compile 'com.squareup.retrofit2:retrofit:1.9.0'
instead of compile 'com.squareup.retrofit2:retrofit:1.9.+'
Use instant run (sometimes)
Instant run speeds up builds immensely – if it works. Keep an eye on weird bugs or inexplicable errors. In bigger projects (or for some other reason less-instant-run-friendly projects) you might be better off spending more time building, and less time debugging non-existent errors.
Update Java
Simply put, Gradle is written in Java, so it’ll benefit from performance improvements in newer JVM implementations. While Android Studio now bundles its own JDK, if you still use older Java on your machine, updating it will also speed up Gradle outside Android Studio.
Profile
Above all, you should always profile and see where there’s place for improvement. Doing this for Gradle build is as simple as providing --profile
option in the command line:
In your build/reports/profile
directory, you’ll find an HTML report with summary of times and details for both configuration phase and task execution. This will give you some insight into which parts of your build deserve attention.
That’s it. Let us know what helped you most, and what other tricks you use to speed up your builds!
Read also: Understanding Android Gradle build files