Android build optimization

Background

When building an Android application, the time necessary for building the app can become a very time-consuming operation. Android developers have to run builds quite often in order to check implemented changes. The recommendations from Google point out that RAM, disk space and OS version as important factors for Android development. However these resources are not the only ones that are important for Android Studio, especially when we are talking about build time. Apart from hardware there are several meaningful optimizations that can be applied to improve your experience significantly.

We focused on build time when we switched to 4K displays because it became cumbersome. We ran several tests for different hardware and configuration:

  • DUT1 – MacBook Pro 13”, early 2015, Intel(R) Core(TM) i7-5557U CPU @ 3.10GHz, 16 GB RAM
  • DUT2 – Macbook Pro 15”, late 2016, Intel Core i7 2,7 GHz, Turbo Boost up to 3,6 GHz (4cores - Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz), 16 GB RAM 2133 MHz, SSD 512 GB, Radeon Pro 455 2 GB, Intel HD Graphics 530 1536 MB
  • DUT3 – MacBook Pro 13-inch (early 2015) – i5-5257U 2,7 GHz, 8GB RAM (Intel Iris Graphics 6100 1536 MB)

For initial research we used a mobile WordPress project from GitHub. It was obvious that we will get better results for better hardware, however the differences between different configurations made us curious to find all the bottlenecks of the build process.

Scenario DUT1 DUT2 DUT3
4K native 4K scaled
Clean & assemble ~ 109s ~ 82s ~ 145s ~ 80s
Code changes + assemble ~ 82s ~ 56s ~ 120s ~ 145s
Layout changes + assemble ~ 105s ~ 68s ~ 144s ~ 183s

Build process

For better understanding of build optimization we should take a closer look to build process itself. In general it’s described in the Android official documentation:

Android Build Process Chart

In short, compilers convert the source code to *.dex files, which are Dalvik executable files. Dalvik is a Java Virtual Machine for Android, that executes *.dex files (optimised for minimal memory footprint). These files are packaged into APK (Android application file) that can be installed on a device. Dalvik is different from regular Java VM, which needs source code to be compiled into Java byte code. With Android 5.0 a new alternative for Dalvik was created (in 4.4 it was optional), which is called ART (Android Runtime) – which is mostly compatible with Dalvik. The primary difference between Dalvik and ART is the compilation approach which is now AOT (Ahead-Of-Time) instead of JIT (Just-In-Time). AOT approach is faster, because compilation is run at the installation step. An application that uses AOT takes more disk space, but is faster at runtime. JIT compilation is executed during the runtime therefore the application can seem slower.

The important note here is that using ART (SDK=21 required) allows for some optimizations to be run during the build process, which speeds it up. The DEX process (sometimes referred as dex-ing) is converting Java compiled code to Dalvik Executables as it uses different bytecode than JVM. More complex applications can hit the limit of method references in a single *.dex file so it might require generating more than one *.dex file, which is known as multidex configuration.

Application source code is built using Gradle. With Gradle 3.4 there are 2 important features that were introduced. The current Android plugin for Gradle (2.3.3) by default use Gradle 3.3.

  • Compilation avoidance – prior to version 3.4 all modules had to be recompiled when a single change was made based on the dependency chain. With compilation avoidance Gradle is smart enough to realize if it should recompile only a single module or the whole dependency chain.
  • Improved incremental build – Gradle analyzes the dependencies between classes, and only recompiles a class when it has changed, or one of the classes it depends on has changed.

Those 2 features unfortunately are not fully compatible with the annotation processing that is widely used in Android development. Annotation processors are mechanisms of code generation (dependency injection, boilerplate code reduction, etc.). They have a negative impact on building application. However there are plenty of libraries that became a standard in the Android development that are built upon annotation processing.

Bottlenecks

In order to define bottlenecks we used DUT3 with 4K scaled display and one of our Android projects. The size of this project was measured with Android Studio plugin – Statistics:

  • 27948 lines of code (44577 total including comments)
  • 865 lines in properties files
  • 32668 lines in xml files
  • 385 Java classes
  • 641 xml files
  • 810 PNG images

Processor (w/o 4K display)

We measured CPU load during the build process with and without 4K display. We observed that the impact of 4K display is meaningful when scaled resolutions is used.

CPU load during build time (~80% user and ~20% system) – 4K scaled

Build Time Chart

CPU load during build time – 4k native

Build Time Chart

In both situations (regardless of display), the processor is heavily loaded. Although the time that it takes to build an app is a bit shorter. Other programs and tools opened are laggy and hardly accessible at the build execution time. All charts show that the CPU load is stronger for 4K scaled display (do note that our tested MacBook has integrated graphics, which might be the reason for the extra overload in scaled 4K display).

Memory

When it comes to memory, Google suggests that 8GB is enough and it appears it is, although it’s highly exploited. It doesn’t affect the build time itself, but do note that it’s important in terms of using Android Virtual Devices. You can observe different pressure with the emulator opened. Memory pressure with Android Studio opened:

Memory Pressure Chart

Memory pressure after running emulator: Memory Pressure Chart

Graphics & display

All gradle tasks take more time when scaled display for 4K is enabled. This might be caused by MacBook integrated graphics, which can’t handle scaled display. We didn’t experience this issue with native 4K display with a MacBook that had a dedicated graphic card. In general a graphics card is not that important for Android app development, but it might be beneficial for testing some apps (especially gaming apps) with the Android emulator.

Disk

We performed our further tests on a machine without SSD disk, however there are plenty of suggestions around the Internet to use SSD disk to speed up Android Studio performance (it includes launching Android Studio, running an app and gradle sync process). In general SSD doesn’t affect the build speed significantly as it’s more processor dependent.

Build optimization techniques

There are several ways of optimizing build time by changing app configuration and gradle files. There are some suggestions and recommendations in the official Android documentation Optimize your build speed. Below you can find some details regarding different options that we’ve used for our project.

Incremental build and instant run

Incremental builds are enabled by default in Gradle 2.1.0-rc1 (2016/4/22) or later so there is no need to adding incremental flag in dexOptions. It is supposed to compile the app based on changed files and their dependencies. We couldn’t take advantage of incremental build because of libraries that we’ve used such as Dagger 2, ButterKnife, Data binding etc.

Instant run is a feature in Android Studio that allows you to push code changes without building a new APK. However this feature is frequently reported as buggy and in fact it even may slow the build speed (examples in Stack Overflow here or here). There are some limitations of instant run that can be found in the official documentation:

  • Some physical devices block Instant run causing errors
  • Android Studio 2.3 uses instant run by default when running debug option
  • Slight performance impact while using instrumented tests and performance profiling
  • Needs configuration for multi-dex-ing
  • Compatibility issues with 3rd party plugins

By disabling Instant run for one of our FM projects we got a build time in a range 30–46s (several attempts, different changes). The same build run with instant run took 38–98s. It appears that some of the libraries we use are affected negatively by instant run.

Split your app into modules

Modularising a project (i.e. extracting rarely used code to libraries) allows Android Studio use cached versions of some modules that don’t change very often. It also improves configuration on demand and parallel execution since some independent modules can be compiled in the same time.

Gradle properties

After enabling multi-dex configuration it was important to add several parameters in gradle.properties file:

  • org.gradle.parallel=true – enables parallel execution (especially beneficial in apps with multiple modules)
  • org.gradle.daemon=true – enables gradle to run in the external process
  • org.gradle.jvmargs=-Xmx1536M – increases Gradle’s heap size in addition to separate dex-in process. It can speed up either clean or incremental build.

Taking advantage of ART by setting minimum SDK to 21

There is a significant difference between SDK 16 and 21 (Android 5.+). Build, which runs for API 21 can be optimized for ART.

Scenario minSDK=16 minSDK=21
Clean & assemble 72s 21
Code changes + assemble 49s 25s
Layout changes + assemble 47s 26s
Resources change + assemble 55s 30s
Code & layout changes 57s 44s

Obviously SDK is determined by business requirements, but there is a solution for that. It is possible to set different SDK for a specific build type – prepared for development specially. Creating a build variant for development is also a suggested step in the official Android documentation.

Other recommendations

There are other optimizations that can be applied. Some of them are recommended not only because of build speed but for other reasons as well – such as memory limitations or disk space usage.

  • Limit compiled resources with resConfig – less disk space used by the app
  • Static values in build.gradle to avoid unnecessary compiling – gradle needs to rebuild dynamic values that can change with every build
  • Static dependency versions instead of dynamic (‘com.android.tools.build:gradle:2.+’) – it also improves stability of the application since you know which version of the library you are using, so you are aware of any updates
  • Configure pre-dex libraries process – it can improve your incremental build however it may slow the clean build
  • Enable build cache (by default in 2.3.0+)
  • Optimize images to svg or WebP formats if possible
  • Separate some build logic to custom gradle tasks so the complexity is excluded and can be possible cached

Optimisation in practise

We measured minimum and maximum speed of building our Android application by applying different optimizations. We ran our tests on DUT2 and DUT3 to check the difference based on hardware we’ve used. For both devices we have significant improvement as we got around 40-50 seconds reduction for DUT3 and 12-25 seconds for DUT2.

Build speed for clean & assemble DUT3 Chart

Build speed for clean & assemble DUT2 Chart

3rd party

JRebel

Incremental build is disabled for annotation processors by default. There is an gradle issue created for that and another for tracking in Google. It was described previously with build process info that incompatibility between incremental compilation and annotation processors are not solved yet by Gradle team. JRebel team claims they managed to do it, so even if the app uses an annotation processor it can benefit from incremental compilation. JRebel has a list of supported libraries that use annotation processors. These are the most popular and used by developers such as Dagger2 or ButterKnife. They reduced compilation time from ~12s to <2s. Compilation time in the FM project takes ~8-14s. Theoretically we could see benefit from JRebel by reducing compilation time. Do note that the initial build is actually slower since all data structures dependencies have to be created.

Genymotion

Android Virtual Device had quite a bad reputation as it was very slow while Genymotion was a fast and memory efficient VM that runs Android OS. It was a good choice when AVD was extremely slow, but with Intel HAXM AVD runs with similar results. However it still offers a bunch of useful features, so it can be worth checking based on your business requirements.

Summary

During our research we defined hardware bottlenecks for Android builds. The most important resource was the processor as it’s loaded heavily during the build process. We came to the conclusion that in fact 8GB RAM is enough but it’s worth to invest in 16GB as the memory usage is pretty high. The results from testing external graphics and 4K display also helped us to define optimal hardware setup.

In addition we took advantage of optimization at the build level so we can benefit from all improvements that Gradle and Android Studio offer such as setting a minimum SDK for development or setting additional gradle properties.

Most Read

1 Team health & the retro
2 How to fold QA into every sprint
3 Cooking with the right ingredients - what's your "Definition of Ready"?
4 Android build optimization
5 Why CSS Grid will drive the web forward

Working with startups from concept to launch

We understand that creating a product is a challenging and risky endeavor and believe that having a partner with experience and know-how is a critical first step.

Learn More

The Startup Journey

From idea to launch we guide you through the startup experience

Learn More