前言
本文旨在通过分析源码一步步分析Robust热修复的实现原理,前半部分首先分析一下Robust思路中运用到的技术方案;后半部分多为源码部分,即Robust对于技术方案的实现与运用。
1、关于Robust
Robust is an Android HotFix solution with high compatibility and high stability. Robust can fix bugs immediately without a reboot.
2、简述Android APK生成原理
首先我们来看一下生成.apk文件时会经过的一些主要步骤:
- 资源文件打包,并生成对应的R.java文件(用于项目中对于资源文件的映射)
- 将aidl编译生成对应的java文件(Android中对于跨进程交互的一种方式)
- 将上述两类和我们编写的源码.java文件通过javac编译成.class文件
- 通过dx脚本将所有的.class文件打包成为一个.dex文件(dex文件为Android中虚拟机所需加载的文件格式)
- 通过apkbuilder将dex文件打包生成.apk文件
3、热修复基本实现思路
- source code中对每一个方法体内进行插桩
- 加载补丁包时,查找到对应方法体及类,使用
DexClassLoader
加载补丁类实现代码修复
原始代码
public long getIndex() {
return 100;
}
插桩后方法体
public static ChangeQuickRedirect changeQuickRedirect;
public long getIndex() {
if(changeQuickRedirect != null) {
//PatchProxy中封装了获取当前className和methodName的逻辑,并在其内部最终调用了changeQuickRedirect的对应函数
if(PatchProxy.isSupport(new Object[0], this, changeQuickRedirect, false)) {
return ((Long)PatchProxy.accessDispatch(new Object[0], this, changeQuickRedirect, false)).longValue();
}
}
return 100L;
}
Patch类
public class StatePatch implements ChangeQuickRedirect {
@Override
public Object accessDispatch(String methodSignature, Object[] paramArrayOfObject) {
String[] signature = methodSignature.split(":");
if (TextUtils.equals(signature[1], "a")) {
//long getIndex() -> a
return 106;
}
return null;
}
@Override
public boolean isSupport(String methodSignature, Object[] paramArrayOfObject) {
String[] signature = methodSignature.split(":");
if (TextUtils.equals(signature[1], "a")) {
//long getIndex() -> a
return true;
}
return false;
}
}
A.代码插桩
ASM技术
首先,我们需要了解一个概念,ASM。ASM是一个Java字节码层面的代码分析及修改工具,它有一套非常易用的API,通过它可以实现对现有class文件的操纵,从而实现动态生成类,或者基于现有的类进行功能扩展。在Android的编译过程中,首先会将java文件编译为class文件,之后会将编译后的class文件打包为dex文件,我们可以利用class被打包为 dex 前的间隙,插入ASM相关的逻辑对class文件进行操纵。
Groovy Transform
Google在Gradle 1.5.0后提供了一个叫Transform
的API,它的出现使得第三方的Gradle Plugin可以在打包dex之前对class文件进行进行一些操纵。我们本次就是要利用Transform API
来实现这样一个Gradle Plugin
。
Transform具体操作的节点如上图所示,对于打包生成dex文件前的.class文件进行拦截,我们来看一看Transform为我们提供的可实现的方法:
方法名 | 含义 |
---|---|
getInputTypes |
Transform需要处理的类型 |
getName |
Transform名称 |
getScopes |
Transform的作用域 |
isIncremental |
增量编译开关 |
transform |
Transform处理文件的核心逻辑代码,此回调参数中可拿到要处理的.class文件集合 |
表格中可见,getNam
、getInputTypes
等方法均为Transform
的配置项,最后的transform
方法需要重点关注,我们想要实现上面的拦截.class文件并进行代码插桩操作就需要在此方法中实现。其中着重看一下方法的型参inputs
,通过inputs
可以拿到所有的class文件。inputs
中包括directoryInputs
和jarInputs
,directoryInputs
为文件夹中的class文件,而jarInputs
为jar包中的class文件。
B.加载补丁
DexClassLoader
关于Java中的ClassLoader
,大家熟知的基础概念就是通过一个类的全名加载得到这个类的class对象,进而可以得到其实例对象。
在Android中的ClassLoader
基本运作远离与Java中类似,不过开发者无法自己实现ClassLoader
进行自定义操作,官方的api为我们提供了两个ClassLoader
的子类,PathClassLoader
与 DexClassLoader
。虽然两者继承于BaseDexClassLoader
,BaseDexClassLoader
继承于ClassLoader
,但是前者只能加载已安装的Apk里面的dex
文件,后者则支持加载apk
、dex
以及jar
,也可以从SD卡里面加载。
从上述的概念我们可以得知,想要实现一个热修复的方案,就需要依赖外部的dex文件,那么就需要使用 DexClassLoader
来帮助实现。
我们来看一下 DexClassLoader
的构造方法:
参数 | 含义 |
---|---|
dexPath |
包含dex文件的jar包或apk文件路径 |
optimizedDirectory |
释放目录,可以理解为缓存目录,必须为应用私有目录,不能为空 |
librarySearchPath |
native库的路径(so文件),可为空 |
parent |
父类加载器(ClassLoader为双亲委派的加载方式,所以需要一个父级的ClassLoader) |
4、Robust的实现
A.代码插桩
这一节中,我们来看一下Robust的具体实现方案以及一些大致的接入流程。
上一节讲述了Robust热修复方案中需要运用到的技术,接下来我们来看一看Robust的具体代码逻辑,如何将上述的思路融汇。
首先看一下Robust为接入用户提供的一个配置相关的文件
<?xml version="1.0" encoding="utf-8"?>