Instant Run: How Does it Work?!
An Android Tool Time Deep Dive
When something’s simple and helpful, most people are satisfied with that. But us engineers — we’re not normal people.
Take Instant Run. It’s a feature in Android Studio that uses ✨magic✨ to significantly reduce the build and deploy times for incremental code changes during your coding / testing / debugging lifecycle.
I say magic, because for the most part that’s how it looks. The first time you hit run or debug, it works like you’d expect — then each time you make a change and hit run or debug again (this time with a ⚡ on the icon), the changes are applied before I’ve had a chance to shift my attention to my phone.
Let’s save the magic for Sunday nights
I prefer my magic to come with a side-helping of dragons, political intrigue, and unexpected beheadings, so I got together with the Android Studio engineering team to get a peek behind the curtain, and learn how Instant Run actually works.
Start with this simple flow chart of a typical build cycle
The goals of Instant Run are really simple:
Remove as many of these steps as possible, and make whatever remains as fast as possible.
In practice that means:
- Build and deploy only the incremental changes.
- Don’t reinstall the app.
- Don’t restart the app.
- Don’t even restart the Activity.
Hot, Warm, and Cold Swaps aren’t references to games played during 70's parties. As far as I know.
Hot Swap: Incremental code changes are applied and reflected in the app without needing to relaunch the app or even restart the current Activity. Can be used for most simple changes within method implementations.
Warm Swap: The Activity needs to be restarted before changes can be seen and used. Typically required for changes to resources.
Cold Swap: The app is restarted (but still not reinstalled). Required for any structural changes such as to inheritance or method signatures.
When you hit run or debug, something like this happens
Your manifest files are merged and packaged, along with your app’s resources, into an APK. Similarly, your source code .java files are compiled into bytecode, converted to .dex files, and they’re also included within your APK.
The first time you hit Run or Debug with Instant Run enabled, Gradle performs some additional tasks
Bytecode instrumentation is added to your .class files, and a new App Server class is injected into your app.
A new Application class definition is also added, which injects custom class loaders and will start the App Server. Accordingly, your manifest is modified to ensure your app uses it (if you’ve created your own Application class, the Instant Run version will proxy yours.)
Instant Run is now… running, so if your make code changes and hit run or debug again, Instant Run will try to shortcut as much of the full build process as possible using a Hot, Warm, or Cold swap.
Before applying Instant Run changes, Android Studio checks that there’s an open socket to an App Server running within an Instant Run enabled version of your app. It confirms the app is running in the foreground, and that its build ID is the version Android Studio expects.
Android Studio monitors which files are changed during development, and runs a custom Gradle task to generate .dex files for only the modified classes.
Those new .dex files are picked up by Android Studio, which deploys it to the App Server running within our app.
Because the original versions of our classes already exist in the running app instance — Gradle has transformed the “updated” versions such that they effectively override those pre-existing classes. Those transformed, updated classes are then loaded by the App Server using the custom class loaders.
From now on, each time a method gets called — anywhere within our app — the instrumentation injected into our original class files communicates with the App Server to see if they’ve been updated.
If so, execution is delegated to the new “override” classes and the new, modified, version of the method will execute instead.
Redirecting methods works well for changes to method implementations, but what about things that are loaded when the Activity starts?
A warm swap restarts the Activity. Resources are loaded when Activities are started, so modifying them requires an Activity restart to force a resource reload.
Currently, changes to any resource results in all of them being re-packaged and transmitted to your app — but we’re working on an incremental packager that will only package and deploy new or modified resources.
Note that a warm swap won’t work for changes to resources referenced within the Manifest — or changes to the manifest itself — because Manifest values are read when the APK is installed. Changes to the Manifest (or manifest-referenced resources) will trigger a full build and deploy.
Unfortunately, restarting the Activity won’t magically apply structural changes. Adding, removing, or changing annotations, fields, static or instance method signatures, or changing parent classes or static initializers will require a Cold Swap.
When deployed, your app and it’s sub-projects are divided into up to 10 slices, each its own dex file; classes are allocated to slices based on their package names. When applying a cold swap, a modified class will require all the other classes within the same slice to also be redexed before that slice is deployed to the target device.
This approach depends on the Android Runtime being capable of loading multiple .dex files, a feature introduced with ART, which is only guaranteed on Android 5.0 (API level 21) devices and higher.
For target devices running API level 20 or lower — and therefore possibly using the DALVIK runtime, Android Studio deploys a full APK.
Instant Run is clever, but it can’t turn back time
Code changes that might otherwise be applied through a Hot Swap, but which affect initializers that were run when the application was first run, you’ll need to restart your app for the changes to take effect.
Instant Run Tips and Tricks
Instant Run is controlled by Android Studio, so only start / restart your debug instance from the IDE — don’t start / restart your app from the device or things will get out of whack quickly.
A more detailed list of tips and tricks is available from the Android Documentation, but here’s a few bullet points to keep in mind.
- Tweak the resources you’re allocating to the Gradle process. If you have at least 2 gig assigned to the Gradle Daemon JVM via the jvmargs setting in the gradle.properties file, dex-in-process will be enabled, and will dramatically improve the speed of all builds — Instant Run and full / clean builds. You’ll want to experiment and observe the effect on your build times to find the value for you.
- The availability of ART in Android 21 means you’ll get the most out of Instant Run by setting your minSdkVersion to 21 or higher. You can create a new product flavor specifically for debugging that sets your minSDK to 21.
- Remember that changes to the manifest will trigger a full build and deploy cycle — so, if your build process automatically updates any part of the app manifest (for example automatically iterating versionCode or versionName) — you’ll want to disable that behavior in your debug build variants.
- Instant Run currently only instruments the main process, so if your app uses multiple processes, Hot and Warm swaps on the other processes will degrade to cold swaps — or full builds if you’re targeting an API Level less than 21.
- If you’re on Windows, Windows Defender Real-Time Protection might be causing Instant Run slowdowns. You can get around that by adding your project folder to the list of Windows Defender exclusions.
- As of this recording, Instant Run didn’t support the Jack compiler, Instrumentation Tests, or deploying to multiple devices simultaneously.
Instant Run is constantly evolving, with the team exploring new techniques to maximize the number of cases that allow for a hot swap, and minimizing the need for cold swaps or full builds.