GreenCat — custom Instant Run plugin for Android.
One day I’ve got an idea to optimize build time somehow for the Android client I’m working with. Because our client is a typical giant multidex application with lot of code, annotation processors and a variety of libraries inside, one can expect very slow build process as a result. It’s very annoying especially when you have a very small code change to run.
Trying to make Agoda project and Google’s Instant Run to be the best friends gives quite poor results. In the best case build time remains the same even for a very small incremental change. Besides Instant Run can cause build issues and thus we need to turn this amazing feature off in Android Studio. It’s a common concern for Android developers. You may find many questions on StackOverflow or Google Developer Groups related to build problems. And the answer is always the same — turn off Instant Run in IDE settings.
Why so slow?
First of all we need to figure out the reason of slowness and take a look what’s happening inside verbose Gradle build logs. Let’s say your project contains several modules. From the Command Line with debug flag:
./gradlew --debug assembleDebug (you may choose another task name)
Then after detailed analysis inside logs you can see that javac compiler was applied to each project module. Furthermore, all annotation processors were applied as well. Note that I’m not speaking here about such amazing build time contributors as Retrolambda or Desugar + dex tool for packing compiled .class files into .dex file(s).
In case of small incremental changes the good question is:
Do I really need to compile all modules, run annotation processors for them and waste time on every Build & Run?
Very often in my practice to fix a bug I need to add/remove/change only few lines of code. Also sometimes it could be a bit more suitable to put debug logs into the code instead of using debugger to find the source of a problem.
GreenCat plugin
Well, I’m fed up with our build time! Finally I decided to try one crazy idea related to Android specific ClassLoader implementation and get answers to some interesting questions.
GreenCat is a custom experimental instant run plugin for incremental builds. As of version 1.0 plugin has lots of limitations and bugs but still very handy to deploy small changes of your code very quickly. It doesn’t use Gradle at all. Please check the list of supported features in the section below.
Check out GreenCat source code and detailed documentation on GitHub: https://github.com/andreyfomenkov/green-cat
Typical use cases: bug fixes, changes in the existing classes. Please don’t consider it as a full-replacement build tool for Android apps because it has a pretty narrow area of usage.
Build time statistics
The diagram below shows up regular vs GreenCat incremental build timings for a typical huge multidex Android project (build times can vary of course). Note that GreenCat doesn’t require to upload and deploy big debug APK file, instead, it pushes small DEX file with incremental changes on SDCard and deploy them with you Android app.
Features list
Check out below the list of features supported or to be done.
Supported:
- Incremental build for existing java code changes (private methods and existing code)
- Devices support with Android 5.x and 6.x
- Devices support with Android 7+
- Supporting Android Studio projects with Retrolambda
To be done:
- Incremental build for big structural code changes (public methods, annotations, inheritance, etc.)
- Annotation processing on demand
- Extension for Espresso tests
- Supporting Android Studio projects with Desugar
- Supporting Android Studio projects with Kotlin
- Incremental build for XML resources (layouts, strings, dimens, etc.)
Concurrent compilation
Typical Android Studio project may contain several modules. If some of them have code changes, GreenCat will be able to perform compilation and .class files transformations independently and concurrently in order to save time and avoid the similar situation shown below:
Under the hood
Let’s take a look inside to understand how it works. Before interacting with a particular class Android virtual machine ClassLoader tries to load it from APK’s DEX file and put into internal map storage. Once class has been loaded it cannot be unloaded if we use native ClassLoader:
Important notice: only the first occurrence of a class in DEX file will be considered as the end of search by VM ClassLoader. During deploying class(es) with incremental changes we somehow must be ahead of the class with the same name in APK’s DEX file. Fortunately, application PathClassLoader gives us a way to add extra DEX files using reflection mechanism. In terms of Instant Run this is a cold swapping process because application needs to be restarted to apply changes. It’s important to follow items order inside dexElements array. Push incremental DEX to SD card and inject inside Android app (my_diff.dex and delta.dex are the same files):
In this way the plugin goes through necessary steps only required for the current incremental changes.
Conclusion
As of version 1.0 you can only deploy changes for the existing Java code or add private methods and variables. Adding public methods or other big structural code changes is not supported for now and it will cause runtime errors. Also this plugin is suitable only for projects based on Retrolambda.
Would be great to hear about any ideas and improvements: droideka@mail.ru or LinkedIn
Time is money.
Happy incremental coding!