Today I installed Android Studio 2.0 Beta 7 and I really enjoyed one of its new features, namely Instant Run. We built a similar feature for NativeScript, named LiveSync, so I was curious to how Instant Run feature works.
The first thing I noticed is instant-run.jar
placed in <my project>/build/intermediates/incremental-runtime-classes/debug
directory. This library provides Server
class in the com.android.tools.fd.runtime
package where are the most interesting things. This class is a simple wrapper around android.net.LocalServerSocket
which opens a unix domain socket with the application package name. And indeed during the code update you can see similar messages in the logcat window.
com.example.testapp I/InstantRun: Received connection from IDE: spawning connection thread
In short, when you change your code the IDE generates new *.dex
file which is uploaded in files/instant-run/dex-temp
directory which is private for your application package. It then communicates with the local server which uses com.android.tools.fd.runtime.Restarter
class. The Restarter
class in an interested one. It uses a technique very similar to the one we use in NativeScript Companion App to either restart the app or recreate the activities. The last one is a nice feature which the current {N} companion app doesn’t support. I find it a bit risky but probably it will work for most scenarios. I guess we could consider to implement something similar for {N}.
So far we know the basics of Instant Run feature. Let’s see what these *.dex
files are and how they are used. For the purpose of this article I am going to pick a scenario where I change a code inside Application’s onCreate
method. Note that in this scenario Instant Run feature won’t work since this method is called once. I pick this scenario just to show that this feature has some limitations. Nevertheless, the generated code shows clearly how this feature is designed and how it should work in general. Take a look at the following implementation.
package com.example.testapp; public class MyApplication extends Application { private static MyApplication app; private String msg; public MyApplication() { app = this; } @Override public void onCreate() { super.onCreate(); int pid = android.os.Process.myPid(); msg = "Hello World! PID=" + pid; } public static String getMessage() { return app.msg; } }
Let’s change msg
as follows
msg = "Hello New World! PID=" + pid;
Now if I click Instant Run button the IDE will generate new classes.dex
file inside <my project>/build/intermediates/reload-dex/debug
directory. This file contains MyApplication$override
class and we can see the following code.
public static void onCreate(MyApplication $this) { Object[] arrayOfObject = new Object[0]; MyApplication.access$super($this, "onCreate.()V", arrayOfObject); AndroidInstantRuntime.setStaticPrivateField($this, MyApplication.class, "app"); int pid = Process.myPid(); AndroidInstantRuntime.setPrivateField($this, "Hello New World! PID=" + pid, MyApplication.class, "msg"); }
By now it should be easy to guess how the original onCreate
method is rewritten. The rewritten MyApplication.class
file is located in <my project>/build/intermediates/transforms/instantRun/debug/folders/1/5/main/com/example/testapp
folder.
public void onCreate() { IncrementalChange localIncrementalChange = $change; if (localIncrementalChange != null) { localIncrementalChange.access$dispatch("onCreate.()V", new Object[] { this }); return; } super.onCreate(); app = this; int pid = Process.myPid(); this.msg = ("Hello World! PID=" + pid); }
As you can see there is nothing special. During compilation the new gradle-core-2.0.0-beta7.jar
library uses the classes like com.android.build.gradle.internal.incremental.IncrementalChangeVisitor
to instrument the compiled code so it can support Instant Run feature.
I hope this post sheds some light on how Android Instant Run feature works.