Reduce your Android build duration

le 05/01/2016 par Rémi Pradal
Tags: Software Engineering

Build duration is a metric that every Android developer should monitor carefully. Indeed (even if you are very confident in the code you produce), you will have to run your project many times every day. When you re-run your code, you need to be able to see the result of your modifications really quickly. Otherwise, two things may happen: something will distract you and you will loose your focus or you will go back to your code and forget to check the effects of your previous run.

Of course this statement seems overplayed when you are working on a small project which will be able to be re-run in less than 30 seconds, but when it comes to huge applications this problematic is real.

We can divide the re-run in two steps: the building phase and the deployment phase. As we can barely reduce the duration of the second step (apart from running your app on an emulator), we will focus in this article on the different levers we can work with to reduce the building phase duration.

Diagnose your build time

We can define two build durations:

  • The “from scratch” build time. It is the build duration when you are running a project for the first time (or when you are performing a gradlew clean before running your project).
  • The “incremental” build time. This duration refers to the minimal build time we have when we re-run the project after a really small source code modification, for instance commenting a single java line.

Of course, in this article, the goal is to be able to have a smaller build duration run after run: we are trying to minimize the “incremental” build time. Thereafter, every mention to a build time will refer to the “incremental” build time notion defined above. Reducing the "from scratch" build time is of course interesting, meanwhile, it is a kind of build we do not perform frequently (i.e. once or twice per day when we clean the project/switch of branch). On the contrary, you will improve significantly your "developer experience"  if you reduce the "incremental" build time as it is a type of build an android developer will run dozens of times per day.

To diagnose your build time you can use the very useful Gradle option --profile. This option will generate a report containing the duration of each subtasks of the task you are running. For instance if you run the command line gradlew assembleDebug --profile, Gradle will generate a report file in the build/reports/profile-[date-of-your-build]. The following screenshot shows an example of a generated report file on a "big" project.

Gradle report profile sample

We can see here the main steps composing an application building:

  • Configuration A usually very quick (a few seconds) step. It depends on how complex your Gradle script is.
  • Dependency resolution This step is almost always instantaneous as the dependencies have already been cached on your computer, even if you have performed a "clean" before. It can take a while if it is the first time ever you build the project or if you add/bump a dependency in your build.gradle. In that case, the duration will only depend on how many libraries you have to pull and how fast your Internet connection is.
  • Task execution This is usually the longest step, which is subdivided in all the different tasks necessary to achieve the main task you want. It includes generally a compiling task, a dexing task and a lot more depending on the task you run and the context. We can notice that some subtasks are way lengthier than others: in the screenshot above the subtasks related to multidexing take a large amount of time.

In almost all the cases, the task execution step represents a huge percentage of the total duration. For instance, in the build task from which the screenshot above is extracted, the execution task step was 42 seconds long over a total time of 50 seconds. The consequence of this observation is that in this case it is in the "task execution" step that we can obtain some build duration cuts.

Set up your IDE and your Gradle configuration

There are some small tricks that you should know that could reduce your Gradle build time in some particular cases. StackOverflow has plenty of scattered tricks. I will try to do here a compilation of what you may read, why the trick can be useful and the situation where you do not have to spend some time trying it because it will not change anything.

  • Gradle daemon.
    Add the line org.gradle.daemon=true in your gradle.properties file.
    This is a tip that you can find really often. The result of this configuration is that Gradle is started before you run an actual Gradle command. It may lead to a few seconds reduction of the build duration. Meanwhile, this solution is useful only if you use the command line for every builds you run. Indeed Android Studio uses natively the Gradle daemon for a long time now [6]. Therefore, this tip will be useless most of the time.

  • Parallel building.
    Add the line org.gradle.parallel=true in your gradle.properties file.
    This Gradle parameter allows parallel module building. It is only interesting if you have numerous modules in your project. The more evenly the build time distribution across the different module is, the better the build time diminution will be. Note that this feature is still experimental: you may experience unexpected behaviours when this option is activated

  • Configuration on demand.
    Add the line org.gradle.configureondemand=true in your gradle.properties file.
    This option will have an impact on the "Configuration" step we described in the first part of this article. When this option is activated, the configuration step of a particular module will be done only if this module has a role to play in the Gradle task you want to run. As for the parallel building, this option will be useful only if your project is split in multiple projects. Even in that case, unless your Gradle scripts are very complicated (or performing time consuming tasks such as a network call), you will not gain more than a few second on your build time.

  • Offline work.
    In Android Studio, click on the "Android Studio" menu, then "Preferences". Navigate in the preferences hierarchy: "Build, Execution, Deployment"-> "Build tools" -> "Gradle". Finally check "Offline work".
    The name of this option speaks for itself! It can have a positive impact if you are working with a not reliable Internet connection. Indeed, depending on your build.gradle configuration, the build can make some network calls (to check if a new library version is available). In offline mode Gradle will use cached versions of the libraries. Of course, if you add a new dependency (or upgrade a dependency to a not cached version), you will have to deactivate this option.

Set your min target to 21

This trick is probably the most efficient in the context of a huge project needing multidexing and which has a minSdkVersion strictly lower than 21.

One of the interesting evolutions in the build process brought by the android sdk version 21 (Lollipop) was the introduction of the Android Runtime (ART). ART and its predecessor Dalvik are the custom Java Virtual Machine (JVM) used by Android. They are compatible with each other : if your application has a minSdkVersion of 15 (for instance), then the dex files generated can be executed on both ART and Dalvik. Meanwhile, if your application has a min sdk of 21 then some optimizations specific to ART can be applied during the app building. One interesting optimization is that ART does not need a main dex file with all the classes invoked before the MultiDex.install(). So, the time-consuming step of identifying what are the classes to put in the main dex file can be skipped. The MultiDex's Android developers documentation page [1] provides extensive explanation of the different optimization ART brought.

Of course, it is not acceptable to put all our big projects to a min sdk of 21! So, we have to find a way to have different build configuration. A first one with our regular min sdk that we could use to generate the apk we deploy in production, and an other one, that would be used by developers when they want to be able to perform fast incremental builds.

A natural solution would be to use a Gradle feature that every developer use (or should use): the buildTypes. Unfortunately it is not possible (yet) to specify a min sdk for a particular build type [2]. Therefore, we have to use another powerful feature proposed by the Gradle android plugin: the flavors.

Specific min sdk targeting thanks to flavors

Flavors allow developers to have different build configurations, ressources or even code, if they want to be able to generate apk with different characteristics. This can be used when you want to generate easily two application with the same source code but with a different branding.

The syntax of a build.gradle file using flavors to have a fast incremental building configuration and a regular one is the following:

Voir le lien github

The result of this is that your build variant number will be duplicated. By selecting the fastBuildDebug variant you will have a significant build time reduction. If you want to test how your app looks with a pre-Lollipop device it is still possible, you just have to select the regularDebug variant. Likewise, the variant you have to select for your production application is now regularProd.

Dealing with your project already having different flavors

This exact method is not adapted when your application has already different flavors. For the rest of this part, let's imagine that your two already-present flavors are brandA and brandB. If we use the code described above we will not be able to have a fast build with the same characteristic as those specified by brandA.

Luckily, there is a solution to this issue: using the multi-dimensional flavors [3]. Multi-dimensional flavors allow us to create different sets of flavors. During the build variant generation, Gradle will not perform substitution of two flavors with different dimension id, instead it will do a juxtaposition. The following Gradle script shows how to use multi-dimensional flavors:

Voir le lien github

Assuming that you have two build types debug and prod, you will now have 2x2x2=8 different build variants expressing the different possible configuration combinations.

Results and limitations of this method

Changing the min sdk reduces tremendously the build duration. For a big project of around 150k lines of code, this method reduces the incremental build time from 2min30s to 1min20s. This contraction can be observed on every computers: I did the same analysis with another computer with approximately the same result 3min to 1min45.

Meanwhile, this method has a few drawbacks: it forces us to increase the build configuration complexity. Indeed if you use some explicit task name in your Gradle scripts or in your continuous integration (CI), you will have to perform changes in each of these references.

Moreover it doubles the build task number. So, if you use some build variant agnostic tasks in your CI, such as gradlew test, the duration of this job will approximately double. Indeed these commands run for every build variants… It can be solved easily by not calling build variant agnostic tasks, but you will have to modify your CI configuration, which can sometimes be painful to do.

Upcoming features: Jack & Jill build system, Android Studio instant run

There might have some features in the future that will impact the build duration. The most significant are probably the new android build chain Jack & Jill [4] and the Android Studio instant run feature brought by the version 2.0 [5].

Jack & Jill build system

Jack & Jill is the new toolchain developed by Google. The goal is to replace the current toolchain which is composed of two complex steps "javac" and "dex" corresponding respectively to the conversion from .java files to .class, and to the conversion from .class files to .dex (the executable format that the android JVM, ART or Dalvick, will be able to read).

The new Jack compiler is able to perform the compilation directly from .class files to .dex. Jill is a tool that converts existing .jar files (generated thanks to the regular tool chain) to files directly usable by the Jack compiler. The following diagram summarizes the inputs and outputs of this new build system.

Jack&Jill build tool mechanism diagram

Jack supports incremental build facilitation. The consequence of this point is that the utilisation of this new toolchain is supposed to increase build speed. It is already possible to try this toolchain in a very simple way: you only have to add useJack = true inside your buildType or flavor block. After some tests on different projects, I was able to compile only a few times: in most projects, I got multiple errors during the Gradle synchronization especially for huge projects.

If this new tool chain is quite promising, it remains too experimental to count on it to reduce our current projects build duration.

Android Studio instant run

The instant run is a feature available in Android Studio 2.0 (still in preview stage) [7], which allows pushing application modifications on an emulator almost instantaneously. Is this feature the definitive solution for our build duration problems? It could be, as it has, on the paper, no more drawbacks than the techniques explained above and is way more convenient to use.

Meanwhile it seems that this feature is not perfectly stable yet : sometimes the modified code is marked as "pushed" whereas the modification is not really applied on the emulator. It is currently stable when it comes to hot resources or xml file swapping. It is really a great improvement, as we generally have to perform numerous incremental builds when we are tuning our xml files.

Conclusion

The build duration is a metric that should be monitored carefully. Indeed it is very easy to let your build duration grow as your app is getting bigger and bigger, without even being aware of it.

There are many ways to reduce your build duration in order to be more efficient when you develop. Meanwhile no tip is universal: you have to identify the bottleneck in your build duration and apply the most appropriate method.

We have seen that there are upcoming features that should bring great solutions to tackle this problematic. If the "stable" tips are not useful enough for you, you should keep a constant eye on the evolutions of these features to see if they are becoming stable enough to be used everyday.

References

[1] http://developer.android.com/tools/building/multidex.html#dev-build [2] https://code.google.com/p/android/issues/detail?id=80650 [3] http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Multi-flavor-variants [4] http://tools.android.com/tech-docs/jackandjill [5] http://android-developers.blogspot.fr/2015/11/android-studio-20-preview.html [6] https://plus.google.com/+AndroidDevelopers/posts/ECrb9VQW9XP [7] http://android-developers.blogspot.fr/2015/11/android-studio-20-preview.html