新建一个项目, 实现简单的 MainActivity , 打开 Android Studio 的 instant run 功能
如果调试设备是小米手机, 需要配置一下
Android Studio 2.3 在小米手机中 调试安装Apk失败
package org.demo.example;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
Log.d("MainActivity", "number=0");
}
});
}
}
instant run 编译后的代码, 可以看到每个方法中都有代理判断
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.demo.example;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.android.tools.fd.runtime.IncrementalChange;
import com.android.tools.fd.runtime.InstantReloadException;
public class MainActivity extends Activity {
public static final long serialVersionUID = -8981860069525532301L;
public MainActivity() { // 对构造函数的特殊处理
IncrementalChange var1 = $change;
if(var1 != null) {
Object[] var10001 = (Object[])var1.access$dispatch("init$args.([Lorg/demo/example/MainActivity;[Ljava/lang/Object;)Ljava/lang/Object;", new Object[]{null, new Object[0]}); // 调用代理对象的 init$args 方法
Object[] var2 = (Object[])var10001[0];
this(var10001, (InstantReloadException)null);
var2[0] = this;
var1.access$dispatch("init$body.(Lorg/demo/example/MainActivity;[Ljava/lang/Object;)V", var2);
} else {
super();
}
}
public void onCreate(Bundle savedInstanceState) {
IncrementalChange var2 = $change;
if(var2 != null) {
var2.access$dispatch("onCreate.(Landroid/os/Bundle;)V", new Object[]{this, savedInstanceState});
} else {
super.onCreate(savedInstanceState);
this.setContentView(2130903040);
Button button = (Button)this.findViewById(2131099648);
button.setOnClickListener(new OnClickListener() {
public static final long serialVersionUID = -6016583034602689826L;
public {
IncrementalChange var2 = $change;
if(var2 != null) {
Object[] var10001 = (Object[])var2.access$dispatch("init$args.([Lorg/demo/example/MainActivity$1;Lorg/demo/example/MainActivity;[Ljava/lang/Object;)Ljava/lang/Object;", new Object[]{null, MainActivity.this, new Object[0]});
Object[] var3 = (Object[])var10001[0];
var3[0] = this;
var2.access$dispatch("init$body.(Lorg/demo/example/MainActivity$1;Lorg/demo/example/MainActivity;[Ljava/lang/Object;)V", var3);
}
}
public void onClick(View v) {
IncrementalChange var2 = $change;
if(var2 != null) {
var2.access$dispatch("onClick.(Landroid/view/View;)V", new Object[]{this, v});
} else {
Log.d("MainActivity", "number=0");
}
}
{
String var3 = (String)((Object[])MainActivity.this)[1];
switch(var3.hashCode()) {
case -1968665286:
return;
case -85614767:
return;
default:
throw new InstantReloadException(String.format("String switch could not find \'%s\' with hashcode %s in %s", new Object[]{var3, Integer.valueOf(var3.hashCode()), "org/demo/example/MainActivity$1"}));
}
}
});
}
}
MainActivity(Object[] var1, InstantReloadException var2) {
String var3 = (String)var1[1];
switch(var3.hashCode()) {
case -1230767868:
super();
return;
case -807788715:
this();
return;
default:
throw new InstantReloadException(String.format("String switch could not find \'%s\' with hashcode %s in %s", new Object[]{var3, Integer.valueOf(var3.hashCode()), "org/demo/example/MainActivity"}));
}
}
}
修改一下MainAcitivity 代码, 使日志打印 number=1
button.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
Log.d("MainActivity", "number=1");
}
});
点击 instant run 编译出 patch 包并运行到测试设备, 可以看到[Project]\app\build\intermediates\transforms\instantRun\debug\folders 中有生成新文件:
其中目录1下面是第一次编译出的 class 文件, 目录 4000 中时 Patch 包中的 class 文件;
MainActivity$1$override 为 MainActivity 类的补丁类, 都是静态方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.demo.example;
import android.util.Log;
import android.view.View;
import com.android.tools.fd.runtime.IncrementalChange;
import com.android.tools.fd.runtime.InstantReloadException;
import org.demo.example.MainActivity;
import org.demo.example.MainActivity.1;
public class MainActivity$1$override implements IncrementalChange {
public MainActivity$1$override() {
}
public static Object init$args(1[] var0, MainActivity this$0, Object[] var2) {
Object[] var3 = new Object[]{new Object[]{var0, this$0, new Object[0]}, "java/lang/Object.()V"};
return var3;
}
public static void init$body(1 $this, MainActivity this$0, Object[] var2) {
}
public static void onClick(1 $this, View v) {
Log.d("MainActivity", "number=1"); // 新的实现
}
public Object access$dispatch(String var1, Object... var2) { // 用于方法分发
switch(var1.hashCode()) {
case -1912803358:
onClick((1)var2[0], (View)var2[1]);
return null;
case -61430165:
return init$args((1[])var2[0], (MainActivity)var2[1], (Object[])var2[2]);
case 1978365703:
init$body((1)var2[0], (MainActivity)var2[1], (Object[])var2[2]);
return null;
default:
throw new InstantReloadException(String.format("String switch could not find \'%s\' with hashcode %s in %s", new Object[]{var1, Integer.valueOf(var1.hashCode()), "org/demo/example/MainActivity$1"}));
}
}
}
目录 4000 中还有一个 AppPatchesLoaderImpl 类, 包含了补丁类信息, 这个类在加载到调试设备上后会被通过反射实例化并调用其 getPatchedClasses 方法来获取补丁类信息
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.android.tools.fd.runtime;
import com.android.tools.fd.runtime.AbstractPatchesLoaderImpl;
public class AppPatchesLoaderImpl extends AbstractPatchesLoaderImpl {
public static final long BUILD_ID = 1501127139210L;
public AppPatchesLoaderImpl() {
}
public String[] getPatchedClasses() {
return new String[]{"org.demo.example.MainActivity$1"};
}
}
反编译已经生成的 APK 发现使用 instant run 第一次编译后, 在AndroidManifest.xml 中插入了一个 ContentProvider , 这个 ContentProvider 已经被打包到 APK 中的 classes.dex 了, APP 启动后 ContentProvider 的 onCreate 方法被调用:
该 ContentProvider 源码位于 [Project]\app\build\intermediates\incremental-runtime-classes\debug\ 目录下,
该 ContentProvider 实现非常简单, 在 onCreate 中判断是否运行在主进程中, 如果是就启动一个 SocketServer 用于接收补丁包数据, 主要逻辑都在 Server 类中, Server 通过 socket 接收到补丁包数据后通过约定的数据格式从 socket 的输入流中解析出 ApplicationPatch 对象数据
public static List read(DataInputStream input)
throws IOException
{
int changeCount = input.readInt();
if ((Log.logging != null) && (Log.logging.isLoggable(Level.FINE))) {
Log.logging.log(Level.FINE, "Receiving " + changeCount + " changes");
}
List changes = new ArrayList(changeCount);
for (int i = 0; i < changeCount; i++)
{
String path = input.readUTF();
int size = input.readInt();
byte[] bytes = new byte[size];
input.readFully(bytes);
changes.add(new ApplicationPatch(path, bytes));
}
return changes;
}
然后调用 handlePatches 方法, 根据 path 判断是 Hot Swap 或 Cold Swap:
private int handlePatches(List changes, boolean hasResources, int updateMode)
{
if (hasResources) {
FileManager.startUpdate();
}
for (ApplicationPatch change : changes)
{
String path = change.getPath();
if (path.equals("classes.dex.3")) {
updateMode = handleHotSwapPatch(updateMode, change);
} else if (isResourcePath(path)) {
updateMode = handleResourcePatch(updateMode, change, path);
}
}
if (hasResources) {
FileManager.finishUpdate(true);
}
return updateMode;
}
private static boolean isResourcePath(String path)
{
return (path.equals("resources.ap_")) || (path.startsWith("res/"));
}