【转载】InstantRun 原理——深度剖析 AndroidStudio 2.0
一、前言
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, 所以屏幕会闪一下 |
|
|
|
需要重新编译整个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的更多相关文章
- 63、Spark Streaming:架构原理深度剖析
一.架构原理深度剖析 StreamingContext初始化时,会创建一些内部的关键组件,DStreamGraph,ReceiverTracker,JobGenerator,JobScheduler, ...
- Mysql binlog应用场景与原理深度剖析
1 基于binlog的主从复制 Mysql 5.0以后,支持通过binary log(二进制日志)以支持主从复制.复制允许将来自一个MySQL数据库服务器(master) 的数据复制到一个或多个其他M ...
- elasticsearch中filter执行原理深度剖析(bitset机制与caching机制)
(1)在倒排索引中查找搜索串,获取document list date来举例 word doc1 doc2 doc3 2017-01-01 * *2017-02-02 * *2017-03-03 ...
- Java引用类型原理深度剖析,看完文章,90%的人都收藏了
本文为synchronized系列第二篇.主要内容为分析偏向锁的实现. 偏向锁的诞生背景和基本原理在上文中已经讲过了. 本文将分为几块内容: 1.偏向锁的入口 2.偏向锁的获取流程 3.偏向锁的撤销流 ...
- Kafka架构和原理深度剖析
Kafka简介 Kafka是一种分布式的,基于发布/订阅的消息系统.主要设计目标如下: 以时间复杂度为O(1)的方式提供消息持久化能力,并保证即使对TB级以上数据也能保证常数时间的访问性能 高吞吐率. ...
- 深度剖析YOLO系列的原理
深度剖析YOLO系列的原理 本文系作者原创,转载请注明出处:https://www.cnblogs.com/further-further-further/p/12072225.html 目录 1. ...
- Java反射机制剖析(四)-深度剖析动态代理原理及总结
动态代理类原理(示例代码参见java反射机制剖析(三)) a) 理解上面的动态代理示例流程 a) 理解上面的动态代理示例流程 b) 代理接口实现类源代码剖析 咱们一起来剖析一下代理实现类($Pr ...
- 深度剖析HashMap的数据存储实现原理(看完必懂篇)
深度剖析HashMap的数据存储实现原理(看完必懂篇) 具体的原理分析可以参考一下两篇文章,有透彻的分析! 参考资料: 1. https://www.jianshu.com/p/17177c12f84 ...
- 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的无状态模 ...
随机推荐
- 【流畅的python】16.1 - 生成器如何进化成协程
在生成器中加入yield关键字后,生成器调用方可以向生成器传入值,只需要使用.send(...)方法就可以传送数据.发送的数据会成为生成器函数中yield表达式的值.所以生成器可以作为协程使用. 协程 ...
- 【Java】用Freemarker完美导出word文档(带图片)
Java 用Freemarker完美导出word文档(带图片) 前言 最近在项目中,因客户要求,将页面内容(如合同协议)导出成word,在网上翻了好多,感觉太乱了,不过最后还是较好解决了这个问题. ...
- ASP.NET Web API 2系列(一):初识Web API及手动搭建基本框架
1.导言 随着Web技术的发展,现在各种框架,前端的,后端的,数不胜数.全栈工程师的压力越来越大. PC端,pad端,移动端App(安卓/IOS)的发展,使得前后端一体的开发模式十分笨重.因此,前后端 ...
- MySQL 和 Navicat Premium 下载及安装全过程
前言: 我对 “MySQL社区版” 的理解是:它只是一个后台服务,它的管理需要用到其他的数据库管理软件,这里我用的是 Navicat Premium,这个软件可以同时为多个数据库提供管理,比如MySQ ...
- FreeRTOS优化与错误排查方法
写在前面 主要是为刚接触 FreeRTOS 的用户指出那些新手通常容易遇到的问题.这里把最主要的篇幅放在栈溢出以及栈溢出j检测上,因为栈相关的问题是初学者遇到最多的问题. printf-stdarg. ...
- 09-01 Tensorflow1基本使用
目录 Tensorflow基本使用 一.确认安装Tensorflow 二.获取MNIST数据集 三.使用Tensorflow训练--Softmax回归 四.使用Tensorflow训练--卷积神经网络 ...
- C语言函数名以及取地址的区别和联系
有时看到如下的代码: /*****************************/ #include <stdio.h> #include <string.h> #inclu ...
- COGS 2089. 平凡的测试数据
[题目描述] 树链剖分可以干什么? “可以支持在树中快速修改一个点信息,快速询问一条链信息” LCT可以干什么? “可以支持树链剖分支持的特性,并且支持快速链接两个棵树,或者断开某条边” 那我现在要出 ...
- Java12新特性 -- switch表达式
传统switch表达式的弊端: 匹配是自上而下的,如果忘记写break, 后面的case语句不论匹配与否都会执行: 所有的case语句共用一个块范围,在不同的case语句定义的变量名不能重复: 不能在 ...
- 小白学 Python(2):基础数据类型(上)
人生苦短,我选Python 引言 前文传送门 小白学 Python(1):开篇 接触一门新的语言,肯定要先了解它的基础数据类型.啥?你问我为啥要先了解基础数据类型? 为了你的生命安全,还是乖乖听我 B ...