Instant Run 源码解析 二 (最新版)

一:前言:

背景:Google对Instant Run一直有着持续的升级,部分前辈们介绍的东西发生了变化,故更新一下部分源码级别的分析。

    参考:https://yq.aliyun.com/articles/202917

             https://www.jianshu.com/p/5947855e3362

    改动综述:
    1、早期的InstantRun实现是修改本身Application为BootstrapApplication的
    2、而后改为在Manifest中添加Service,监听代码改动
    3、目前最新的是在Manifest中添加ContentProvider,并执行Service。

二:概述:

1、代码背景:
    step1:为了简单演示,只写了一个MainActivity、一个SimpleToast类,内部调用Toast.show方法
Instant Run 源码解析 二 (最新版)_第1张图片
    step2:运行,记得 勾选InstantRun
         暂停:查看代码 在app/build/intermediates/transform目录会生成工程中所有的class,如下图:
           Instant Run 源码解析 二 (最新版)_第2张图片
其中的class是被修改过的。
Instant Run 源码解析 二 (最新版)_第3张图片
    每个方法前都多出了一个IncrementalChange var1 = $change ,并且判断了var1是否为空,不为空则执行原本的代码
    上述代码通过判断$change变量来执行不同的逻辑。这也是InstantRun中hot swap的实现原理,通过插桩的方式在每一个函数中植入$change变量及其相关逻辑,当相关代码被修改时,利用反射的方式将$change重置,从而执行修改后的逻辑已达到热修复的目的。
      不太明白?没关系,后面从源码理解。 获取问题 + 1
Manifest:在intermediates/Manifest/instant-run/debug中,
Instant Run 源码解析 二 (最新版)_第4张图片
被系统添加了一个InstantRunContentProvider,这个Provider是干嘛的呢? 获取问题 + 2

step3:同时修改MainActivity、SimpleToast两个类的方法实现、运行。
app/build/intermediates/transform多出来一些代码哦~
       Instant Run 源码解析 二 (最新版)_第5张图片
AppPatchesLoaderImpl内通过getPatchedClasses()方法获取,这次InstantRun中有哪些类被修改过了,
因为我们同时修改了SimpleToast和MainActivity两个类。SO:
public String[] getPatchedClasses() {
return new String[]{"com.example.xxx.irsourcecode.SimpleToast", "com.example.xxx.irsourcecode.MainActivity"
};
xxx$override.class:

Instant Run 源码解析 二 (最新版)_第6张图片
xxx$override都实现了IncrementalChange接口,内部添加了access$dispatch()方法。
2、app-debug.apk
选择Instant-run 运行之后,会在 build/output/目录下生成一个app-debug.apk

但是这个apk并不是普通的apk。如果单独调用 adb install -r app/build/output/app-debug.apk 是无法进行安装的。
此处通过dex -> jar以及相关可视化工具查看apk文件。
Instant Run 源码解析 二 (最新版)_第7张图片
其中并没有自己写的代码。 PS:此处的这些代码很有用哟~
通过查看adb的log发现,AS在安装的时候并不是只安装了一个app-debug
adb install-multiple -r xxx/app/build/intermediates/split-apk/debug/slices/slice_4.apk
xxx/app/build/intermediates/split-apk/debug/slices/slice_5.apk
xxx/app/build/intermediates/split-apk/debug/slices/slice_8.apk
... (一共从0到9、10个slice_x.apk)
xxx/app/build/outputs/apk/app-debug.apk

系统将我们的代码分成了10个apk。里面都包含dex文件。当代码量小的时候,只有其中个别有代码。
尝试了两次,一次在slice_3 、一次在slice_7
如果代码量大,则大致会平均分配在不同的slice_?.apk中。
具体如何分配暂不清楚。。

二:源码层级:
源码入口 -> InstantRunContentProvider
内部判断了是否是主线程,调用Server.create()方法创建Server,代码如下。
     Instant Run 源码解析 二 (最新版)_第8张图片
(在PC端和移动端建立连接的时候,PC端为Client,用于发出代码改动的声明,移动端为Server,用于接受Client的声明)

Instant Run 源码解析 二 (最新版)_第9张图片
     1、根据传递进来的packageName 创建LocalServerSocket。
     2、startServer()内启动了SocketServerThread等待客户端传来请求。

Server内部类 SocketServerThread 线程:     

     1、获取Server构造函数内初始化的serverSocket。打开Socket

2、内部又创建了SocketServerReplyThread线程,最终跳转到其handle方法
3、判断错token数
Instant Run 源码解析 二 (最新版)_第10张图片
SocketServerReplyThread:

private void handle(DataInputStream paramDataInputStream, DataOutputStream paramDataOutputStream)
        throws IOException
{
    long l = paramDataInputStream.readLong();
    if (l != 890269988L)
    {
        Log.w("InstantRun", "Unrecognized header format " + Long.toHexString(l));
        return;
    }
    int i = paramDataInputStream.readInt();
    paramDataOutputStream.writeInt(4);
    boolean bool1;
    if (i != 4)
    {
        Log.w("InstantRun", "Mismatched protocol versions; app is using version 4 and tool is using version " + i);
        return;
        if (Restarter.getForegroundActivity(Server.this.context) == null) {
            break label246;
        }
        bool1 = true;
        paramDataOutputStream.writeBoolean(bool1);
        if (Log.isLoggable("InstantRun", 2)) {
            Log.v("InstantRun", "Received Ping message from the IDE; returned active = " + bool1);
        }
    }
    for (;;)
    {
        i = paramDataInputStream.readInt();
        label246:
        Object localObject;
        switch (i)
        {
            case 2:
            default:
                if (!Log.isLoggable("InstantRun", 6)) {
                    break;
                }
                Log.e("InstantRun", "Unexpected message type: " + i);
                return;
            case 7:
                if (!Log.isLoggable("InstantRun", 2)) {
                    break;
                }
                Log.v("InstantRun", "Received EOF from the IDE");
                return;
            bool1 = false;
            break;
            case 3:
                if (Log.isLoggable("InstantRun", 6)) {
                    Log.e("InstantRun", "Unexpected message type: " + i);
                }
                break;
            case 4:
                if (Log.isLoggable("InstantRun", 6)) {
                    Log.e("InstantRun", "Unexpected message type: " + i);
                }
                break;
            case 5:
                if (!authenticate(paramDataInputStream)) {
                    break;
                }
                localObject = Restarter.getForegroundActivity(Server.this.context);
                if (localObject != null)
                {
                    if (Log.isLoggable("InstantRun", 2)) {
                        Log.v("InstantRun", "Restarting activity per user request");
                    }
                    Restarter.restartActivityOnUiThread((Activity)localObject);
                }
                break;
            case 1:
                if (!authenticate(paramDataInputStream)) {
                    break;
                }
                localObject = ApplicationPatch.read(paramDataInputStream);
                if (localObject != null)
                {
                    bool1 = Server.hasResources((List)localObject);
                    i = paramDataInputStream.readInt();
                    i = Server.this.handlePatches((List)localObject, bool1, i);
                    boolean bool2 = paramDataInputStream.readBoolean();
                    paramDataOutputStream.writeBoolean(true);
                    Server.this.restart(i, bool1, bool2);
                }
                break;
            case 6:
                localObject = paramDataInputStream.readUTF();
                Activity localActivity = Restarter.getForegroundActivity(Server.this.context);
                if (localActivity != null) {
                    Restarter.showToast(localActivity, (String)localObject);
                } else if (Log.isLoggable("InstantRun", 2)) {
                    Log.v("InstantRun", "Couldn't show toast (no activity) : " + (String)localObject);
                }
                break;
        }
    }
socket开启后,开始读取数据,先进行一些简单的校验,判断读取的数据是否正确。然后依次读取文件数据。
  • 如果读到7,则表示已经读到文件的末尾,退出读取操作
  • 如果读到2,则表示获取当前Activity活跃状态,并且进行记录
  • 如果读到3,读取UTF-8字符串路径,读取该路径下文件长度,并且进行记录
  • 如果读到4,读取UTF-8字符串路径,获取该路径下文件MD5值,如果没有,则记录0,否则记录MD5值和长度。
  • 如果读到5,先校验输入的值是否正确(根据token来判断),如果正确,则在UI线程重启Activity
  • 如果读到1,先校验输入的值是否正确(根据token来判断),如果正确,获取代码变化的List,处理代码的改变(handlePatches,这个之后具体分析),然后重启
  • 如果读到6,读取UTF-8字符串,showToast
  • 当读到1时,获取代码变化的ApplicationPatch列表,然后调用handlePatches来处理代码的变化
此处以为 HotSwapPatch 的为例:
private int handleHotSwapPatch(int paramInt, ApplicationPatch paramApplicationPatch)
  {
    if (Log.isLoggable("InstantRun", 2)) {
      Log.v("InstantRun", "Received incremental code patch");
    }
    for (;;)
    {
      try
      {
        paramApplicationPatch = FileManager.writeTempDexFile(paramApplicationPatch.getBytes());		//将patch文件写入临时存放目录,以为 dex-temp命名
        if (paramApplicationPatch == null)
        {
          Log.e("InstantRun", "No file to write the code to");
          return paramInt;
        }
        if (Log.isLoggable("InstantRun", 2)) {
          Log.v("InstantRun", "Reading live code from " + paramApplicationPatch);
        }
        localObject = FileManager.getNativeLibraryFolder().getPath();							
        localObject = Class.forName("com.android.tools.fd.runtime.AppPatchesLoaderImpl", true, new DexClassLoader(paramApplicationPatch, this.context.getCacheDir().getPath(), (String)localObject, getClass().getClassLoader()));
      }
      catch (Throwable paramApplicationPatch)
      {
        Object localObject;
        boolean bool;
        Log.e("InstantRun", "Couldn't apply code changes", paramApplicationPatch);
        paramInt = 3;
        continue;
      }
      try
      {
        if (Log.isLoggable("InstantRun", 2)) {
          Log.v("InstantRun", "Got the patcher class " + localObject);
        }
        paramApplicationPatch = (PatchesLoader)((Class)localObject).newInstance();
        if (Log.isLoggable("InstantRun", 2)) {
          Log.v("InstantRun", "Got the patcher instance " + paramApplicationPatch);
        }
        localObject = (String[])((Class)localObject).getDeclaredMethod("getPatchedClasses", new Class[0]).invoke(paramApplicationPatch, new Object[0]);
        if (Log.isLoggable("InstantRun", 2))
        {
          Log.v("InstantRun", "Got the list of classes ");
          int j = localObject.length;
          int i = 0;
          if (i < j)
          {
            String str = localObject[i];
            Log.v("InstantRun", "class " + str);
            i += 1;
            continue;
          }
        }
        bool = paramApplicationPatch.load();
        if (!bool) {
          paramInt = 3;
        }
      }
      catch (Exception paramApplicationPatch)
      {
        Log.e("InstantRun", "Couldn't apply code changes", paramApplicationPatch);
        paramApplicationPatch.printStackTrace();
        paramInt = 3;
      }
    }
    return paramInt;
  }
然后反射调用AppPatchesLoaderImpl类的load方法,需要说明的是, AppPatchesLoaderImpl继承自抽象类AbstractPatchesLoaderImpl ,并实现了抽象方法
如下是AbstractPatchesLoaderImpl抽象类的源码,注意看load方法:
     Instant Run 源码解析 二 (最新版)_第11张图片
1、getPatchedClass(),内部就是AS帮我们自动生成的,被修改过的类的名称的数组。
2、修改的类名称+"$override" 也是系统帮我们生成的新类。
3、获取原先创建的 $change变量,
override类在加载后,因为Var1 !-= null 所以会调用其access$dispatch方法。修改调用为更新后的方法。
不点击⚡️ enhanced无代码
改变方法参数,enhanced无代码,猜测因为走的是冷启动。


PS:相关后续问题

    1、源码地址  :https://android.googlesource.com/platform/tools/base/+/studio-master-dev/instant-run/

    2、能否通过改变dex再打包成jar以验证?Log.v 能打出来么?

    3、resource.ap_ 这个 和 resource.arsc文件有何不同?

    4、新版本为何去掉了 BootstrapApplication    Service    Instant_run.zip这套东西。

你可能感兴趣的:(Android)