We have previously published articles that, surprisingly, are not about JRebel for Android, but are useful for every Android developer. We’ve talked about your Gradle build times, about getting started with Retrofit 2 and so on.
Today, I’ll take a look at build cache that is coming to Android development toolbelt in the future. This can potentially have a great impact on improving build times. Which is universally a good thing, since no one likes to spend more time waiting for the project to build.
Android Studio 2.3 and the build cache
As of writing, the latest available version of Android Studio is 2.3 Canary 2. It has a number of improvements: Incremental SDK Updates, PNG to WebP conversion, and more. But what gets me most excited is the new build cache feature.
Although first introduced in Android Studio 2.2 and actually a part of the Gradle Android plugin 2.2, it wasn’t enabled by default. This has been changed in Android Studio 2.3 Canary. If you like to live on the edge, you can get it right now. Note that it’s not entirely clear if it will remain enabled by default. The AS team put it this way: “With your feedback, we are aiming to graduate it to stable and enable it by default in Android Studio 2.3 or 2.4.”
Why should you care about the build cache? The simple answer is that it will make your clean builds faster. That is when you run “gradle clean build” or similar commands.
How does it make the build faster? By caching the already dexed libraries outside of Gradle’s cache management. Whenever you do Clean from Android Studio or ./gradlew clean, libraries in the build-cache will persist and get reused the next time you need to build the APK. You can see the cached files in your home folder, under .android/build-cache. Let’s take a look at what we can find in there.
It is a list of files and folders with obscure names. The 0 byte files are used for file locking. This is necessary because the same cached files can be used across multiple projects. The lock file will prevent two projects from trying to read/write the same cache simultaneously.
Exploded aar caches
This leaves us with folders. Two types are used here: dex caches and exploded aar caches. The exploded aar caches are provided in their output folders. For example, the input file of 220674f5fc7186b424e032744f0eeb413d469b54 folder contains:
The folder name is generated using
sha1sum of the input file. In this case, it’s the android-maps-utils library. The exploded aars get cached during the dependency analysis if none is found.
The dexed library caches have a similar structure. The input file contains:
COMMAND=PREDEX_LIBRARY FILE_PATH=/Users/Sten/.android/build-cache/220674f5fc7186b424e032744f0eeb413d469b54/output/jars/classes.jar FILE_HASH=cf251baf39f5c5138224b67b4106eb6331abbd13 BUILD_TOOLS_REVISION=25.0.0 JUMBO_MODE=false OPTIMIZE=true MULTI_DEX=false
The file path is pointing us to the previous folder that we looked at. Here we have the dexed version of android-maps-utils library. The keys and values inside the input file define the key of each cache entry. For example, build tools revision 25.0.0 and 25.0.1 would have a different dex cache, as the BUILD_TOOLS_REVISION value is different. But the same exploded aar would be used, because the command and maven coordinates that we looked at previously are still the same.
Here the output is a file instead of a folder. Unzipping it gives us:
73 12-06-16 16:07 META-INF/MANIFEST.MF 0 12-06-16 16:07 META-INF/ 87000 12-06-16 16:07 classes.dex 0 12-06-16 16:07 com/ 0 12-06-16 16:07 com/google/ 0 12-06-16 16:07 com/google/maps/ 0 12-06-16 16:07 com/google/maps/android/ 0 12-06-16 16:07 com/google/maps/android/clustering/ 0 12-06-16 16:07 com/google/maps/android/clustering/algo/ 0 12-06-16 16:07 com/google/maps/android/clustering/view/ 0 12-06-16 16:07 com/google/maps/android/geometry/ 0 12-06-16 16:07 com/google/maps/android/heatmaps/ 0 12-06-16 16:07 com/google/maps/android/projection/ 0 12-06-16 16:07 com/google/maps/android/quadtree/ 0 12-06-16 16:07 com/google/maps/android/ui/
As you can see, it’s just the folder structure and classes.dex file.
Multidex and API level 21
Cached dexes are used differently based on the combination of multidex and whether you are targeting API level 21 or later.
Let’s start with the case where we have no multidex defined. In this case it does not matter if we are targeting API level 21 or later. The cached dex files will be used and dex merge will be done. In the apk we’d see one classes.dex file which contains all the application classes and libraries.
Applications with a minSdkVersion lower than 21 and multidex will not be able to make use of the predex libraries in the build cache. This is because legacy multidex does not support predexed libraries. The Gradle plugin always dexes all the application and library classes in one go.
The last option leaves us with multidex enabled and API level later than 21. In this case the cached dex files in build-cache folder will directly go into the APK. Each library will have a classes.dex file inside the APK. This is also why API 21 is the fastest option for building during development.
I also did some tests with the 2015 iosched app, with no multidex and min API level 21 on my 2013 Macbook Pro (with 2.4GHz i5, 16GB of memory and a 250GB SSD). I ran 5 clean builds from the command line with Gradle Daemon enabled, with build cache enabled and disabled respectively. Here are the results I got by taking the median of the 5 runs for each setup:
Clean build without build cache
The result is an impressive drop from 18.7 to 6.5 seconds. It’s also clearly visible how much time is spent on the :android:transformClassesWithDexForDebug task, going from 12.1 to 1.7 seconds. The time you save will heavily depend on how many libraries you have in your projects.
If you haven’t tried Android Studio 2.3 yet, I recommend that you do just that. You will see how much time you’ll win from clean builds. If you are not interested in moving to the Canary build, you can simply enable the build cache in Android Studio 2.2 and Android Gradle plugin 2.2. Add
android.enableBuildCache=true to your gradle.properties file in your project root.
This also means that your clean build won’t actually be clean anymore. Dexed libraries will get cached outside of your regular build folder to .android/build-cache in your home folder. Luckily you can use
./gradlew cleanBuildCache to clear the contents of the build cache and get a completely clean build.
In this post we looked at build cache and how it will improve the build times for Android projects. Like I said before, if you want to give it a go – grab Android Studio 2.3 Canary 2. In our next post we’ll take a detailed look at another goodie: aapt2. If you want to fast forward and know what it does, I recommend listening to this androidbackstage podcast. In short, it will give you yet another boost to reduce the build times. This time by making the resource packaging incremental.
If you’d like to eliminate your build times almost completely and reload your application on the device or emulator immediately after changing its code check out JRebel for Android. It really works!