Adam
Posted on October 29, 2021
When I joined my current company a few months ago, the incremental builds of our Android app took an average of 14 minutes on my machine. You can imagine how wildly unproductive that made me feel, so I went on a journey to speed up our build times.
It's down to a little over 1 minute now, so I'll share some of the things I learned along the way. Some of these settings were already configured, and some weren't, but I'll share everything anyway, but please don't apply these changes blindly to your project and instead use the gradle profiler to ensure that they work for your existing setup.
Easy wins
-
Use the latest versions of Gradle, JVM, and the plugins you use in your project.
We were using AGP 4.2, and by updating to AGP 7, we were able to take advantage of the perfromance improvmenets promised in Gradle v7. If you're stuck on AGP 4.2 for some reason, then enable file-system watching manually.
-
Enable parallel execution
If you're working on a multi-module project, then forcing Gradle to execute tasks in parallel is also an easy performance gain. This works with the caveat that your tasks in different modules should be independent, and not access shared state, which you shouldn't be doing anyway. To enable it, add
org.gradle.caching=parallel=true
to yourgradle.properties
file. -
Enable build caching
This works by storing and reusing outputs produced by other builds if the inputs haven't changed. One feature of this is task output caching. It leverages Gradle's existing
UP_TO_DATE
checks, but instead of only reusing outputs from the most recent build on the same machine, it allows Gradle to reuse outputs from any earlier build in any location on the machine. When using a shared build cache for task output caching this even works across developer machines and build agents. To enable this, addorg.gradle.caching=caching=true
to yourgradle.properties
file.
General advice
Before we move on to the next section, here's a quick primer on the Gradle build lifecycle. Every Gradle build has 3 phases:
-
Initialization: this is where Gradle decides which modules are going to take part in the build, and creates a
Project
instance for each of them. - Configuration: this is when the project objects are configured, and all the build scripts of all projects which are part of the build are executed. This phase is where you need to pay the most attention because it's executed with every build, so if you're firing off a network request here for some reason, please don't.
- Execution: here, Gradle executes the tasks that were created and configured during the configuration phase.
Now on to to some random bits of advice!
-
Really think about the plugins you add to your project
Every plugin you add to your project adds time to the configuration phase, even if it doesn't do anything. So go through your plugins and remove the ones you're not using.
-
If you're using the Crashlytics or Firebase Performance Monitoring plugins, disable them for debug builds.
We saw massive improvements by making these changes.
android {
...
buildTypes {
debug {
ext.enableCrashlytics = false
FirebasePerformance {
instrumentationEnabled false
}
}
}
}
You'll also want to disable Crashlytics at runtime for debug builds like this:
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG)
-
Convert build logic to static tasks
If you have a lot of logic in your
build.gradle
, consider converting it to static Gradle tasks so Gradle can cache their results and alleviate their effects on your project's configuration phase time. For example, if you have code that determines the versionNumber based on the current Github branch, that's a good candidate to start with. While we're on this topic, always check and make sure yourbuild
gradle` files are as lean as possible. We removed a few legacy tasks/code snippets that didn't make sense in the scope of our project anymore this way. -
Enable the parallel garbage collector if you're using JDK 9+
You might want to profile this change first before enabling it for your project, but it can be enabled by appending the string
-XX:+UseParallelGC
to yourorg.gradle.jvmargs=
in thegradle.properties
file, or just by addingorg.gradle.jvmargs=-XX:+UseParallelGC
if you haven't customized these settings before. -
Use the gradle-doctor plugin
I know I just said you should really think before adding plugins to your project, but this one's only purpose is to improve your builds by giving you warnings about issues it finds in your project, and you can remove it once you're done.
-
Enable non-transitive R classes
-
Enable configuration caching
This is an experimental feature, so proceed with caution, but I can confirm that it was absolutely magical for us. Remember the configuration phase we talked about above? Well, this feature caches its results and reuses them for subsequent builds, similar to how build caching caches and reuses task outputs. You can enable it by adding the line below to your
gradle.properties
file, but please read about it hereorg.gradle.unsafe.configuration-cache=true
-
Disable the Jetifier
Here's an excellent blog post about this by Adam Bennett
Posted on October 29, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.