美文网首页
Android Instant-Run 最新原理剖析

Android Instant-Run 最新原理剖析

作者: 雁过留声_泪落无痕 | 来源:发表于2017-08-16 17:07 被阅读0次

    一、背景

    1.参考:深度理解Android InstantRun原理以及源码分析

    2.时间:2017.08.16

    3.AS版本:2.3.3

    4.TAG:InstantRun(开启此 TAG 可以看到 instant-run 的相关输出,帮助理解)

    二、最新原理

    上面那篇文章其实已经分析得比较到位了,但是随着 google 对 instant-run 的升级,文章里的一些东西已经发生了变化,比如不再使用 IncrementalClassLoader,也不再使用 BootstrapApplication 进行代理了。

    1.运作流程

    a.程序如何运行起来的?

    在设置里勾选使能 instant-run 的情况下,运行程序,程序会在手机上正常跑起来,同时在 app/build/outputs/apk/ 目录下会生成一个名为 app-debug.apk 的文件。我们现在把 app 卸载掉,单独以 adb install -r app-debug.apk 的方式安装 app 到手机,点击桌面图标启动 app,会发现报 class not found 的错误!

    我们解压 app-debug.apk 可以看到只有两个 dex,使用 d2j-dex2jar.sh 转换成 jar 文件再用 JD-GUI 查看,可以看到,这里只有 instant-run 相关的宿主代码,根本没有我们的业务代码。这也是驱动我去看 instant-run 代码的最原始的动力,奇了怪了,没有业务代码你是怎么跑起来的呢?

    上面的参考文章里说了业务代码在 instant-run.zip 中,但是我们解压 app-debug.apk 也没有发现这个 zip 包啊!经过一番折腾,最后才发现,AS 使用了如下的安装命令:

    adb install-multiple -r -p com.xxx.example.titan /xxx/Titan/app/build/intermediates/split-apk/debug/slices/slice_1.apk /xxx/Titan/app/build/outputs/apk/app-debug.apk

    我勒个乖乖,难怪呢,原来使用了 install-multiple 命令同时安装了两个 apk,此时找到 slice_1.apk,解压确实看到了我们的业务代码。一样的,业务代码的每个方法前都插入了代码,参考上面给出的文章。

    同时,root 过的手机,进入 /data/app/com.xxx.example.titan/ 目录下,也是可以发现我们的业务代码的,说明业务代码确实是安装到了手机上的。

    b.程序和 as 如何通信的?

    这里,建议看官们下载 google 官方的源码阅读,大致有如图所示的代码:

    instant-run 源码目录

    主要通信的类是 InstantRunService 和 Server 两个类,现从 app-debug.apk 中解压出来的类来看,InstantRunService 已经不用了,改成了 InstantRunContentProvider,如图:

    app-debug.apk 解压出来的 dex 代码

    /**
    * Service which starts the Instant Run server; started by the IDE via
    * adb shell am startservice pkg/service
    */
    public classInstantRunServiceextendsService {
    ...
    }

    从官方的注释可以看出,通过 as 先启动 service(现在是 content provider),然后在 provider 中,创建了 Server 实例,并启动了 server,开启了 socket 等待 as 的连接。

    然后就是协商协议版本,读取消息头,决定后续处理步骤等工作了。

    2.源码之 Server#handleHotSwapPatch() 

    a.客户端(AS 端)

    当改动了我们的业务代码时,比如 MainActivity.java 的 onCreate() 方法,然后点击 AS 上的闪电一样的符号,表示开始进行热部署,此时 AS 会生成一个 dex 文件,包含两个类,一个名为 MainActivty$override;另外一个叫 AppPatchesLoaderImpl,这个类继承 AbstractPatchesLoaderImpl,主要是要靠 AS 实现一个虚方法:

    public abstract String[] getPatchedClasses();

    另外还有一个由 AbstractPatchesLoaderImpl 实现了的方法:

    public boolean load() {
    ...
    }

    b.服务端(app 端)

    AS 通过 socket 告诉 app,我有代码改动了,需要进行部署。此时,app 通过 socket 得到这个消息,然后进入 handleHotSwapPatch() 方法进行处理。

    首先,把 AS 生成的那个 dex 文件存入 /data/data/files/instant-run/ 目录下,然后构造一个 DexClassLoader,把这个 dex 作为寻找路径。

    然后反射创造 AppPatchesLoaderImpl 实例,调用其 getPatchedClasses() 方法,获取到一个列表,这个列表表明了哪些代码发生了改变,即需要“修复”的类列表,比如这里,列表中只有一个数据,就是 MainActivity。

    跟着,就进入了 AbstractPatchesLoaderImpl#load() 方法了,通过一个循环,读取所有列表,比如读到 MainActivity 时:

    1.先通过反射创建一个 MainActivity$override 实例;

    2.再用 ClassLoader 把 MainActivity load 进来,由于在最开始 gradle 编译 app-debug.apk 时,就使用 asm 等字节码操作工具给每个类都生成了一个 $change 静态成员,同时在每个方法的开头都插入了逻辑,判断 $change 是否为空,为空则走正常逻辑,否则走修复后的逻辑;这里通过反射直接把 MainActivity$override 实例赋值给了 $change 成员;同时,如果以前已经热部署过了一次或者多次,会把 $change 成员的 $change 字段置为 true,表明之前的过期了。

    3.源码之 Server#restart()

    由于修改了 MainActivity 的代码,所以会走到:

    if(updateMode ==UPDATE_MODE_WARM_SWAP) {
    ...
    Restarter.restartActivityOnUiThread(activity);
    }

    进而会辗转走到:

    Restarter#restartActivity

    至此,MainActivity 得以重新加载,当运行到 onCreate() 的时候,此时 $change 成员已经不为空,且指向了 MainActivity$override 类的实例,也就是说 MainActivity#onCreate() 原有的逻辑已经执行不到了,而是执行了修改过后的逻辑,也就是 MainActivity$override#onCreate() 中的逻辑了。

    三、最后

    其中还涉及到资源相关的处理,这里就不罗列了。

    Instant-Run 的好处就是只会把有修改的地方打成 dex 传到手机进行部署,主要是节约了开发时间;

    这里强烈推荐一下阿里的 freeline,也是非常不错的一个及时部署得工具,有个缺陷就是会侵入我们的业务代码,比如需要在 Application 中进行初始化,不过这都好办,比如通过判断是否是 debug 包来初始化;同时通过增加 Property 来判断是否需要在 build.gradle 中应用插件。

    相关文章

      网友评论

          本文标题:Android Instant-Run 最新原理剖析

          本文链接:https://www.haomeiwen.com/subject/rvvirxtx.html