Instant Run概述
Instant Run 是 Android Studio2.0 之后新增的一个运行机制,能够减少开发人员第二次及以后的构建时间。
在没有Instant run之前,编译部署应用程序的流程如下图:
传统的编译部署,需要重新安装APP和重启 APP。这样会很耗时。
使用Instant Run会避免这一情况。
Instant Run基于更改的部分进行构建和部署。
Instant Run部署有三种方式,Instant Run会根据代码的情况来决定采用哪种部署方式。
- hot swap——修改一个现有方法中的代码时会采用hot swap,他不需要重启当前Activity即可实现使更改的代码运行
- warm swap——修改现有资源文件时采用warm swap,他需要重启Activity。
- cold swap——当增删改一个字段、方法或类时会采用cold swap。他需要重启APP。
Gradle Transform
Gradle Transform是Android官方提供给开发者在项目构建阶段即由class到dex转换期间修改class文件的一套api。
Instant Run修复原理
- 当第一次构建APP时,Instant Run利用Transform API在每一个类中注入一个叫change的字段
- change实现了incrementalChange接口。
- 在每一个方法中添加一个逻辑判断,如果change不为空就执行change的accessdispatch方法,否则执行原方法的逻辑。
- 当我们修改完对应的代码点击run后,Instant Run会生成对应的patch文件。
- patch文件中补丁类的名字是修改类的名字+$override,这个类实现了IncrementalChange接口
- 同时生成一个AppPatchsLoaderImpl类,这个类维护着一个所有修改过的类的列表
- 通过AppPatchsLoaderImpl将修改过的类中的change字段赋值为生成的xxx$override。
这样就实现了对代码的修改。
ClassLoader 修复代码
类加载方案基于Dex分包方案。
65535限制
Android系统有一个65536限制,意思是应用中引用的方法数不能超过65536个。
产生一个限制的原因是Dalvik Bytecode的限制,Dalvik指令集的方法调用指令invoke-kind索引为16bite,最多应用65536个方法。
LinearAlloc限制
Dalvik中的LinearAlloc是一个固定的缓冲区,当方法数超出了缓存区的大小时会报错。
针对这两个限制,产生了Dex分包方案。Dex分包方案主要做的是在打包时将应用代码分成多个Dex。
将应用启动时必须用到的类和这些类的引用类放到主Dex,其他代码放到次Dex,当应用启动时先加载主Dex,等到启动后再动态加载次Dex。
我们都知道,Android的类加载主要有两个:PathClassLoader和DexClassLoader,他们都是BaseDexClassLoader 的子类。
其中PathClassLoader在应用启动时创建,用于加载apk的dex文件。
DexClassLoader可以加载SD卡上包含dex的.jar和.apk文件。
BaseDexClassLoader 有个字段private final DexPathList pathList
,BaseDexClassLoader 的findClass()、findResource()均是基于pathList实现的。
DexPathList 里面有一个Element数组,Element内部封装了DexFile ,DexFile用于加载dex文件,所以每个dex文件对应一个Element。
当通过BaseDexClassLoader 查找一个类时,会通过以下路线进行查找:
- 调用 BaseDexClassLader 的 findClass(String name)方法
- 接着交给 DexPathList 的findClass()方法
- 然后遍历Element数组,调用Element的findClass()方法
- Element的findClass()方法内部调用DexFile的loadClassBinaryName方法查找类。
当查找到了就直接返回,没有找到就去下一个Element查找。
所以,根据上述的查找流程,我们只需将我们修复后的类打包成Dex文件,存放到Element数组的最前面,这样就会优先从修复后的dex中查找并返回
具体操作的步骤就是
- 获得APP的PathClassLoader
- 通过dex包路径和父加载器创建我们的DexClassLoader
- 反射得到两个加载器的pathList
- 反射得到两个pathList的dexElements
- 创建新的dexElements,将我们的Element放入dexElements最前面
- 反射赋值给PathClassLoader
代码
由插件化那里搬过来的,忽略插件字眼~
DexClassLoader dexClassLoader = new