Android, better build time

Android, better build time

How to improve your Android project build time

Featured on Hashnode

Hi, today I'll share about the process that we used to improve the build time of the project that I'm currently working, from 6-8 minutes to 3-5 minutes. The build time depends on several variables, so we can not define an exactly number. The progress so far is not too much but, we have a lot to do and 3 minutes per build in a day make some difference later.

Before we start, keep in mind that every project is different and has his own particularities, so maybe, what we did here will not work for you. You need to study your project and check what will make sense to do or not.

I like to work with these optimizations because, I can see with time things getting better, and build time is something really important on a developer routine, if you have to wait 8 minutes to see a layout change, this is awful. So take some time, and a step back in your project and improve it, it is not ok to be frustrated with your work for things like this.

Tool

First, you need to install the gradle-profiler tool, we will use it to measure the build time of the project, you can also use the Gradle --scan command, but I like the Gradle profiler because he offers more features, with the advanced profiling scenarios.

I use the following command to trigger the build profile. You need to create a file on the root of the project called performance.scenarios.

gradle-profiler --benchmark --scenario-file performance.scenarios clean_build

I will not explain how to set up the file, on the gradle-profiler page you have a great documentation on how to write your scenarios, but, basically this file will simulate a change in the project on every file that we usually change, XML, fragments …

# gradle-profiler --benchmark --scenario-file performance.scenarios clean_build  

default-scenarios = ["clean_build"]  

clean_build {  
    title = "Clean Build"  
    tasks = ["assembleDevBuildDebug"]  
    cleanup-tasks = ["clean"]  
    show-build-cache-size = true  
    apply-abi-change-to = "{path}"  <-- Replace with your file path
    apply-non-abi-change-to = "{path}"  
    apply-android-resource-change-to = "{path}"  
    apply-android-resource-value-change-to = "{path}"  
    apply-android-manifest-change-to = "{path}"  
    apply-android-layout-change-to = "{path}"  
    warm-ups = 3  
    iterations = 5  
}

Process

I will describe here everything that we changed on the project to achieve a better build time, but maybe some of them are not directly related to the improvement.

Synthetics

This is one of the tasks that maybe did not improve the build time itself but, improved the project, Kotlin synthetics was deprecated and removed from latest versions of Kotlin, so we stop using synthetics and start using view bind which offer some cool features.

Reference

Unused code

We had a lot of files and codes that were not being used on the project anymore, since at this point our cache is not optimized fewer items to be build results in less building time. When you generate the release version of your application, R8 will shrink all resources and try to remove all the unused code, but having them on the project if you are not using them, brings a lot of issues.

Disable some Gradle features

We disabled all these features, you can add these flags on the gradle.properties, not every project or modules need these features, so Gradle build them unnecessarily.

android.defaults.buildfeatures.buildconfig=false  
android.defaults.buildfeatures.renderscript=false  
android.defaults.buildfeatures.resvalues=false  
android.defaults.buildfeatures.shaders=false
android.defaults.buildfeatures.aidl=false

android.databinding.incremental=true

Keep in mind that, if one of your modules use one of these features you need to enable them for that specific module, example, BuildConfig files are a common way to store keys, and different modules in a project use that, so all you have to do is add this to your module gradle.kt file.

buildFeatures {  
    ...
    resValues = true  
}

Create some modules

We have a lot of content on the web about this one, having different modules in your project helps Gradle to manage the cache, and it invalidates fewer parts of the cache to build again. In the future, I'll post another article about modularization of the project.

But I recommend you to start with small pieces of the project, extract them to a single module and with time you will start seeing some benefits, and in the future if that module does not make sense anymore, just move everything to the new place and delete it.

Gradle cache

By default, this feature is disabled by Gradle, but it can bring some benefits, so I recommend you to enabled it. All you have to do is, open your gradle.properties file and add the following code:

org.gradle.caching=true

Some other features that can help speed up the build time:

org.gradle.daemon=true  
org.gradle.parallel=true
org.gradle.vsf.watch=true
org.gradle.unsafe.configuration-cache=true
org.gradle.configureondemand=true <- Multi module project benefits  
org.gradle.jvmargs=-Xmx4G -XX:+UseParallelGC"

kotlin.incremental.useclasspathSnapshot=true

On the Gradle Documentation you can read more about these features and what they do.

Remove unused / unnecessary dependencies

With time, it is common to have unused code and dependencies that developers forget to remove or something happen that they stay there for a long time, and having fewer dependencies on the project means less time that Gradle needs to sync.

So it is always good to have some sort of recurring task to check all the dependencies of the project and decide, if it makes sense, to continue using them. And of course, have some sort of filter about adding new ones.

Remove Jetifier

This is probably the most impacting one and most difficult one to get rid of, if you don't know what Jetifier means, you can check here. But basically is a tool that google created to migrate legacy dependencies to AndroidX, and this runs on the project build, so you are losing time migrating libraries that maybe or probably has already updated to newer AndroidX versions.

In this step you need to be patient because it will be hard to remove all the dependencies, so take your time, it will not happen out of the blue.

You can use this tool to help you identify which one of your dependencies are preventing you to disable Jetifier on your project, once you have a successful build using the tool, you can open your gradle.properties files and remove the following line:

android.enableJetifier=true

Of course, if your project does not have this flag, you do not need to worry about this step.

Updating Gradle

This is self-explanatory, just use the latest version of Gradle, so you can get all the improvements that they are doing over the years, to help us build projects faster.

Gradle doctor

This is more a tool that will help you to identify possible problems in your project that are impacting the build process, you can check the documentation here.

Future

Here I describe some of the tasks that we plan to work in the future to improve even more our project.

More modules

Create more modules in the project, so we can benefit more from the cache and have a more isolated code. Multimodule has a lot of benefits and if your projects is big or is getting big, it is time to start planning to create more modules.

Disable plugins on Debug

There are some plugins that we use on our project like, crashlytics, Firebase performance, bugsnag ... That they often do not need to be on the debug build, and this impacts the build time of your project. You can use this, to check on how to disable plugins on a specific flavour.

Split files

With Kotlin, we have the ability to create more than one class, enum, interface … In the same file, and as they explain here, this is bad for build time, because Gradle use files to compose his cache, and when you have multiple things inside the same file you lose some benefits, because everything on the file composes the same cache, and if you change Gradle will recompile things that did not suffer changes.

Update Kotlin to 1.7.x

Kotlin 1.7 has introduced some features that improve the incremental compilation, here we have some cool data about the new process. Keep in mind that Kotlin 1.7 introduced some breaking changes to the language, so you will probably need to change some code to support this migration.

Migrate java code

On this same vide JetBrains, explains that can impact the build time if you have Java and Kotlin code in the same code base, Kotlin is the main language for Android development for some years already, so I strongly recommend you to migrate all the Java code to Kotlin, create a plan and start the refactoring.

Gradle remote cache

If your project has a CI/CD pipeline that runs on Jenkins, Bitrise … You can benefit from the build cache that the CI generated, but this is tricky because sometimes if you have a bad connection this will increase more your build time, since you need to download the cache and after start the build process. You can read more about remote cache here.

Gradle Version Catalog

Gradle has introduced this features which helps us to decrease the build time of the project when you change a library version. On most of the approaches that we have today, like buildSrc module, what happens is, when you change a library version, Gradle invalidates the cache, and starts a fresh build, and with the version catalog features, we can change versions and still use the cache.

This feature is still not fully supported by the Android Studio, but I strongly recommend taking a look and watch for future updates.

Here you can read more about it.

Did you find this article valuable?

Support Rodrigo Martins by becoming a sponsor. Any amount is appreciated!