文档章节

InstantRun 原理——深度剖析 AndroidStudio 2.0

网易云捕
 网易云捕
发布于 2016/05/26 14:50
字数 1833
阅读 85
收藏 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

下面分析 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。

 

 

© 著作权归作者所有

网易云捕
粉丝 9
博文 9
码字总数 12305
作品 0
杭州
私信 提问
加载中

评论(0)

AndroidStudio怎样导入jar包

AndroidStudio用于开发安卓Apk非常地方便,但是它的很多设置都与Eclipse不一样。比如给项目中添加新的jar,在AndroidSutdio中就没有Java编译路径的入口,就很难找到添加jar的入口。不过换一种...

傲娇字符
2015/08/03
90
0
AS 升级之Gradle's dependency cache may be corrupt问题解决办法

版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/u013132758。 https://blog.csdn.net/u013132758/article/details/60589629 问题: 今天,发现自己的AndroidStudio可以升...

紫雾凌寒
2017/03/06
0
0
关于Android Studio升级至3.1出现AAPT2 error的解决办法

转载自:https://www.jianshu.com/p/2a63c5710ee9 关于Android Studio升级至3.1出现AAPT2 error的解决办法 www.jianshu.com 新建example试一下是否完全成功后,出现了红字--"Error:java.uti...

犀牛有脾气
2018/12/16
0
0
android studio基本使用零碎整理

android studio进行单元测试:http://jingyan.baidu.com/article/454316ab7a5711f7a7c03a9a.html AndroidStudio怎样导入jar包:http://jingyan.baidu.com/article/e6c8503c7190b7e54f1a1893......

智能小松鼠
2015/07/03
316
0
解决Android killer APK 编译失败,无法继续下一步签名

报错特征   自己使用AndroidStudio生成了一个Demo App,用来测试Androd killer的反编译的功能,结果报错了,报错信息如下: 解决方式 解决方式一 删除报错文件   参考:https://www.52p...

Mysticbinary
2019/11/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

如何从Joomla垃圾箱中删除文章

Joomla允许您删除文章,但是除非您采取其他步骤,否则它不会永久删除它们。 Joomla的垃圾桶类似于PC和Mac的垃圾桶。将项目发送到垃圾桶是可以撤消的操作。 在这个简短的教程中,我将向您展示...

六艺网络专注于Joomla
8分钟前
13
0
图解kubernetes命令执行核心实现

K8s中的命令执行由apiserver、kubelet、cri、docker等组件共同完成, 其中最复杂的就是协议切换以及各种流拷贝相关,让我们一起来看下关键实现,虽然代码比较多,但是不会开发应该也能看懂,祝你...

8小时
12分钟前
6
0
sh和bash之间的区别 - Difference between sh and bash

问题: When writing shell programs, we often use /bin/sh and /bin/bash . 在编写shell程序时,我们经常使用/bin/sh和/bin/bash 。 I usually use bash , but I don't know what's the d......

技术盛宴
24分钟前
21
0
spring - 使用profile来管理环境信息

程序一般都会有开发环境、测试环境以及线上环境,这些环境下程序运行依赖的基础一般不同,例如在有数据源访问的程序中,开发时可能使用了嵌入式数据库,而到测试环境上会使用独立的mysql,正...

閒散人員
27分钟前
7
0
如何实现项目流程自动化

多人协作复杂的任务,团队成员间的分工和沟通就非常必要。现在Zoho projects 项目管理软件中,配合使用蓝图功能,讲让工作事半功倍。 蓝图功能可以解决繁琐的邮件沟通问题,并使任务更加有序...

Zoho云服务
30分钟前
20
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部