×

[Android 学习笔记] instant-run 源码学习 ( 2 )

96
afluy
2017.07.27 17:27* 字数 425

新建一个项目, 实现简单的 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 中有生成新文件:

TIM截图20170727162452.png

其中目录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 方法被调用:


image.png
image.png

该 ContentProvider 源码位于 [Project]\app\build\intermediates\incremental-runtime-classes\debug\ 目录下,

image.png

该 ContentProvider 实现非常简单, 在 onCreate 中判断是否运行在主进程中, 如果是就启动一个 SocketServer 用于接收补丁包数据, 主要逻辑都在 Server 类中, Server 通过 socket 接收到补丁包数据后通过约定的数据格式从 socket 的输入流中解析出 ApplicationPatch 对象数据

  public static List<ApplicationPatch> 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<ApplicationPatch> 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<ApplicationPatch> 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/"));
  }
Android学习笔记
Web note ad 1