天天看点

InstantRun原理(1)——初始化逻辑

android studio 2.0开始支持 instant run 特性, 使得在开发过程中能快速将代码变化更新到设备上。之前,更新代码之后需要先编译一个完整的新apk,卸载设备上已安装的这个 apk (若有),再 push 到设备安装,再启动。有了 instant run 特性之后,只需要 push 一些增量到设备上,直接执行,可以为开发人员节省大量时间。当然 instant run 特征只在 debug 时有效,对发布 release 版没有任何影响。

instant run 通过 hot swap, warm swap, code swap 三种 swap 来实现。android studio 会根据代码的改变自动决定 push 哪种 swap 到设备上,并根据不同的 swap 执行不同的行为。

代码改变内容

instant run 行为

修改一个实例方法或者一个静态方法的实现

hot swap: 这是最快的情况,下次调用该方法时直接使用更新后的方法

修改或者删除一个资源

warm swap: app 保护运行状态,但是会自动重启 activity, 所以屏幕会闪一下

增加、删除或修改((1)注解 (2)成员变量/静态变量/方法签名)修改类的继承关系、实现的接口修改类的静态代码块利用动态资源id改变资源顺序

cold swap(api level >= 21): 需要重启app若api level < 21, 则需要重新编译整个app

修改 androidmanifest.xml修改被 androidmanifest.xml 引用的资源修改 widget ui

需要重新编译整个app

接下来我们就以一个简单的例子来介绍instantrun的原理。

首先我们来创建一个简单的demo,demo很简单,只有一个activity,activity中有一个button:

运行该demo,效果很简单就不截图了。重点来看下其apk文件:将打包出来的apk解压后结构如下:

InstantRun原理(1)——初始化逻辑

classes.dex:

InstantRun原理(1)——初始化逻辑

classes2.dex

InstantRun原理(1)——初始化逻辑

可以看到,两个dex文件完全没有包含任何工程代码,看上去全部都是无关代码。其实这些代码都是instant-run.jar中的代码,也就是说instantrun工程在进行apk打包的时候将intant-run.jar包打入到了apk中。但问题是,我们的代码(也就是上文中<code>mainactivity</code>)去哪儿了?

其实用户代码都被写入到apk文件中的instant-run.zip中去了,将instant-run.zip解压后可以看到:

InstantRun原理(1)——初始化逻辑

可以看到在这个路径下还有很多dex文件,而我们的代码就被放在slice_9-classes.dex中,至于instant-run.zip中的打包/分包逻辑,为啥用户代码会被打入到 <code>slice_9-classes.dex</code> 中我还不是太清楚,知道的同学可以给我留言:

InstantRun原理(1)——初始化逻辑

可以看到,在用户代码的每一个函数中都被插入了这样一段代码:

上述代码通过判断<code>$change</code>变量来执行不同的逻辑。这也是<code>instantrun</code>中<code>hot swap</code>的实现原理,通过插桩的方式在每一个函数中植入<code>$change</code>变量及其相关逻辑,当相关代码被修改时,利用反射的方式将<code>$change</code>重置,从而执行修改后的逻辑已达到热修复的目的。

另外我们再来看下androidmanifest.xml文件:

可以看到,工程中的application被篡改成了<code>com.android.tools.fd.runtime.bootstrapapplication</code>,这个类输入intents-run.jar包,不难猜测,application的初始化过程也被instant-run所代理了。

看到这里,一个instant-run功能的大致结构基本就清晰了:

<code>instantrun</code>工程实际上是一个宿主工程,用户代码以资源的形式放入到apk中

<code>instantrun</code>工程通过<code>com.android.tools.fd.runtime.bootstrapapplication</code>代理app的初始化过程,猜测在初始化的过程中,<code>com.android.tools.fd.runtime.bootstrapapplication</code>会去加载用户代码

<code>instantrun</code>在编译代码时会通过插桩的方式给每一个函数植入一段代码,从而在需要时hook

看完了编译阶段,接下来看下运行时阶段相关原理。由于<code>com.android.tools.fd.runtime.bootstrapapplication</code>代理了整个应用的初始化工作,从而成为了整个应用的入口。我们就从<code>com.android.tools.fd.runtime.bootstrapapplication</code>开始入手。

首先来看下<code>attachbasecontext</code>:

我们依次需要关注的方法有:

createresources → setupclassloaders → createrealapplication → 调用realapplication的attachbasecontext

2.2.1.1 createresources

该方法主要是判断资源resource.ap_是否改变,然后保存resource.ap_的路径到externalresourcepath中。

2.2.1.2 setupclassloaders

该方法将用户代码dexlist注入到一个自定义classloader实例中,并将该classloader设置为默认class loader:<code>bootstrapapplication.class.getclassloader()</code>的父loader。<code>incrementalclassloader</code>源码如下:

上述代码总过做了两件事:

将一个delegateclassloader设置为系统classloader的父loader

将用户代码dex文件路径设置为该classloader加载路径

由于classloader的加载采用双亲委托模式,所以当需要加载用户代码时,系统classloader会首先找到<code>bootstrapapplication.class.getclassloader()</code>,而<code>bootstrapapplication.class.getclassloader()</code>

又会委托其父loader也即我们创建的<code>delegateclassloader</code>实例,该实例会负责完成用户代码的加载。

2.2.1.3 createrealapplication

<code>createrealapplication</code>的目的是创建真实的application实例。真实的application保存在<code>appinfo</code>中,如果用户自定义了application,则直接创建该application实例;否则则创建系统默认的application实例。

接下来我们再来看下<code>com.android.tools.fd.runtime.bootstrapapplication</code>的<code>oncreate</code>方法:

在oncreate()中我们需要注意以下方法:

monkeypatchapplication → monkeypatchexistingresources → server启动 → 调用realapplication的oncreate方法。

2.2.2.1 monkeypatchapplication

该方法将当前所有app的application替换为realapplication:

替换activitythread的minitialapplication为realapplication

替换mallapplications 中所有的application为realapplication

替换activitythread的mpackages,mresourcepackages中的mloaderapk中的application为realapplication

2.2.2.2 monkeypatchexistingresources

该方法的作用是替换所有当前app的massets为newassetmanager。

monkeypatchexistingresources的流程如下:

如果resource.ap_文件有改变,那么新建一个assetmanager对象newassetmanager,然后用newassetmanager对象替换所有当前resource、resource.theme的massets成员变量。

如果当前的已经有activity启动了,还需要替换所有activity中massets成员变量

判断server是否已经启动,如果没有启动,则启动server。然后调用realapplication的oncreate方法代理realapplication的生命周期。

至此instantrun的初始化工作就算完成了,接下来就是在监听到代码变化后热更新了。总结一下,instantrun在初始化阶段主要做了以下几部分工作:

代码编译阶段对每一个用户代码中的方法进行插桩,这是hot swap的基础

创建宿主apk,用户代码全部写到instant-run.zip中

创建宿主application(bootstrapapplication),并在宿主application初始化时:

通过注入classloader的方式,加载位于instant-run.zip中的用户代码

利用反射的方式注入真正的application

当server启动后,会持续监听是否有代码更新,如果有便加载到本地后进行热更新。具体的更新逻辑,请看下一篇博客。

继续阅读