Blazing Fast Android Builds
At Google I/O today, the Android Studio team released the first preview version of the Android Gradle plugin 3.0, based on Gradle 4.0 M2. It brings major performance improvements, especially for builds with plenty of subprojects. In this blog post, we will explain what you can expect from this preview version and how the Android Studio and Gradle team achieved these improvements. Before diving into this let’s look back at what goals led to the creation of the current Android build system.
The Complexity of Mobile Development
Developing mobile applications is inherently more complex than building traditional web or server applications of similar size. An app needs to support a wide array of devices with different peripherals, different screen sizes, and comparatively slow hardware. The popular freemium model adds another layer of variety, requiring different code paths for free and paid versions of the app. In order to provide a fast, slim app for every device and target audience, the build system needs to do a lot of the heavy lifting up front.
To improve developer productivity and to reduce runtime overhead, the Android build tools provide several languages and source generators, e.g. Java, RenderScript, AIDL and Native code. Packaging an app together with its libraries involves highly customizable merging and shrinking steps. The Android Studio team was faced with the challenge of automating all of these without exposing the underlying complexity to developers. Developers can focus on writing their production code.
Last but not least, developers expect a build tool to manage their dependencies, be extensible and provide deep IDE integration.
Gradle is ideally suited for those challenges and the Android Studio team created a fantastic Android build tool on top of the Gradle platform.
The performance challenge
No matter how elegant and extensible the plugin and no matter how seamless the IDE integration, when things take too long, developers become unproductive and frustrated. The Android Studio team has made steady progress on performance over the last years. The emulators became much faster, the time to deploy an app decreased by orders of magnitude with Instant Run and other improvements. These steps have now exposed the build itself as the final bottleneck. The Android Studio team and the Gradle team have continuously improved the performance of the plugin and the platform, but so far this has not been enough. Fundamental design issues preventing great performance.
So Gradle Inc. and Google teamed up in late 2016 to get this situation under control. The work was split up into three areas:
- General improvements to Gradle and its Java support: Faster up-to-date checking, compile avoidance, stable incremental compilation and parallel dependency downloads.
- General improvements to the Android tools, like dex and code shrinking, including incremental dexing.
- New APIs for variant aware dependency management in Gradle and an Android plugin that uses these new APIs.
The latter allowed the Android Studio team to finally get rid of a lot of inefficient workarounds that they had to build because of these missing APIs.
To understand why variant aware dependency management is so important, imagine you have an app which depends on a single library. Both of them support ARM and x86 architectures, both have a free and a paid version and both of them can be built for debug and production. This creates a total of 8 variants. But at any given point in time, a developer is only working on exactly one variant, e.g. the “free x86 debug” variant.
Up until now, the Android plugin had to inspect the app’s dependencies very early in the build lifecycle to select the right variant of the library to build. This early phase is called configuration time, during which Gradle determines what tasks it needs to run in what order. More work at configuration time means slower builds no matter which tasks the user selected. It also affects how long it takes to synchronize the build with the IDE. The Android plugin’s eager dependency inspection lead to a combinatorial explosion of configuration time as more subprojects were added to a build.
This completely changes with Gradle’s new variant aware dependency management. The Android plugin can now provide matching strategies for the different variant dimensions (like product flavor and build type), which Gradle uses during dependency resolution to select the correct variant of the upstream library. This completely removes the need to resolve dependencies at configuration time and also allows the Android plugin to only build the parts of the library that the app needs.
In a particularly large app with 130 subprojects, the time it took to configure the project dropped from 3 minutes to 10 seconds with Android 2.3 tools to under 2 seconds with Android 3.0. The clean build time dropped from over 5 minutes to about 1 minute. The effect on incremental builds is dramatic when combined with new compile avoidance functionality. Making a single-line change and assembling the project is down to about 9 seconds. For monolithic projects these numbers won’t be as impressive, but they show that the build system now works very efficiently with modularized apps.
Last but not least, the Android Studio team is going to make the Android plugin 3.0 compatible with the Gradle build cache. The build cache allows build outputs to be reused across clean builds and across machine boundaries. This means that developers can reuse build outputs generated by CI and build pipelines can reuse results from earlier stages. It also speeds up switching between feature branches on developer machines. Preliminary tests are promising, the clean build for the large Android app mentioned above dropped from 60s to about 20s when using the cache.
Give it a try
The Android Studio team has written up a comprehensive migration guide. There may be compatibility issues with community plugins, as many of them depended on internals that work differently now.
If you are developing Android projects, give the preview a try and tell us how much your build times improved out of the box. Try modularizing your app a bit more and splitting
implementation dependencies for even bigger performance gains. You can use Build Scans and its timeline view to get deep insight into the performance of your build, which tasks were executed and how long they took.
If you are an Android plugin author, the new version might require some changes for your plugin to stay compatible. Please file an issue if you encounter any problems while migrating.
You can expect more improvements on the Gradle side. For instance, we are currently working on parallel task execution by default.
It is also safe to expect more performance smartness from the Android Studio team including Android Studio optimizations to do as little work as possible when syncing the project. The Gradle and Android Studio teams are collaborating on this as well.
Support for community plugins will improve as the alpha versions mature and plugin authors adjust to it. The more people provide feedback, the faster these great improvements can be released as stable.