Android Instant-Run 最新原理剖析

一、背景

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 中应用插件。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容