一、框架特性对比
特性 | Tinker | Robust |
---|---|---|
即时生效 | 否 | 是 |
方法替换 | 是 | 是 |
类替换 | 是 | 否 |
类结构修改 | 是 | 否 |
资源替换 | 是 | 否 |
so替换 | 是 | 否 |
支持gradle | 是 | 是 |
补丁包大小 | 较小 | 一般 |
Rom体积 | 较大 | 较小 |
成功率 | 较高 | 最高 |
二、补丁包生成对比
1、Tinker
Tinker支持命令行工具和Gradle两种打包方式,更推荐gradle的方式。
1.1、命令行工具生成
命令行工具tinker-patch-cli.jar
提供了基准包与新安装包做差异,生成补丁包的功能。具体的命令参数如下:
java -jar tinker-patch-cli.jar -old old.apk -new new.apk -config tinker_config.xml -out output_path
参数与gradle基本一致,新增的sign参数,需要输入签名路径与签名信息。在编译时需要将TINKER_ID
插入到AndroidManifest.xml
中。例如
<meta-data android:name="TINKER_ID" android:value="tinker_id_b168b32"/>
1.2、Gradle生成
调用assembleDebug
编译,生成的基准包保存在build/bakApk
中。

然后修改要修复的代码,生成补丁包前需要修改build.gradle
中的三个参数
- oldApk:基准APK文件
- applyMapping:基准包的 Proguard mapping.txt 文件路径
- applyResourceMapping:基准包的资源 R.txt 文件路径
调用tinkerPatchDebug
, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/
。然后将补丁包patch_signed_7zip.apk
推送到手机的sdcard中就可以修复Bug了。

2、Robust
自动化补丁工具
打补丁包前需要保存好mapping
及methodsMap.robust
文件。打补丁包时,将之前保存的这两份文件放置在app/robus/
目录下以供Robust去对比两个版本的差异来生成补丁。

在App的build.gradle
下将之前注释掉的’auto-patch-plugin’
插件给打开,然后就可以修改代码了。
apply plugin: 'com.android.application'
//制作补丁时将下面这个apply打开,auto-patch-plugin紧跟着com.android.application
//apply plugin: 'auto-patch-plugin'
apply plugin: 'robust'
需要改动的方法上面添加@Modify
注解,对于Lambda表达式,则需要在修改过的方法里面调用RobustModify.modify()
方法。
修改方法前
public void loadData() {
Log.d("修改方法前");
}
修改方法后
@Modify
public void loadData() {
Log.d("修改方法后");
}
新增方法
@Add
public void add() {
Log.d("新增方法");
}
改完代码后,运行和生成线上apk同样的命令,即可生成补丁,补丁目录app/build/outputs/robust/patch.jar
,推送到手机的sdcard中就可以修复Bug了。
三、代码修复原理对比
目前有三种修复方案,类加载方案、Instant Run方案和底层替换方案。Tinker使用的是类加载方案,而Robust使用的则是Instant Run的方式。
1、类加载方案(Tinker)
ClassLoader在加载类的过程中,会调用DexPathList的findClass方法,Element中封装了DexFile用于加载Dex文件。
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
当查找class时,遍历dexElements有序数组,通过Element查找到Class,查找到后返回,没有查找到会接着在下一个Element中查找。修改一个有bug的类(Key.class)后,将类(Key.class)打包成包含dex的补丁包Patch.jar放到dexElements数组的第一个元素,当查找类时首先会找到Patch.dex中修改后的类(Key.class),由于ClassLoader的双亲委托模式,将不会再加载后面有bug的类(Key.class),如下图:

Tinker将新旧APK包做差异生成patch.dex,将patch.dex与apk中的classes.dex合并,再将新的classes.dex放到dexElements数组的第一个元素,以此来替换有bug的类。由于加载后的类是无法删除的,如果重新加载新的类需要重新启动App,所以这种方法无法即时生效。
2、Instant Run方案(Robust)
Robust会在每个类中注入一个静态变量,在方法前插入一段逻辑控制代码。
注入前
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;
}
获取到补丁后,会创建DexClassLoader加载补丁,通过被修复的类信息找到该类,反射将changeQuickRedirect对象赋值为补丁对象,changeQuickRedirect将不为空,执行到方法时就会调用补丁的方法而不是原方法的逻辑,这样就起到了热修复的目的。

四、注意事项
1、Tinker
- Tinker不支持修改
AndroidManifest.xml
,Tinker不支持新增四大组件(1.9.0支持新增非export的Activity) - 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码
- 在Android N上,补丁对应用启动时间有轻微的影响
- 不支持部分三星
android-21
机型,加载补丁时会主动抛出"TinkerRuntimeException:checkDexInstall failed"
- 对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标
2、Robust
- 内部类的构造方法是private(private会生成一个匿名的构造函数)时,需要在制作补丁过程中手动修改构造方法的访问域为public
- 对于方法的返回值是this的情况现在支持不好,比如builder模式,但在制作补丁代码时,可以通过如下方式来解决,增加一个类来包装一下(如下面的B类),
method a(){
return this;
}
改为
method a(){
return new B().setThis(this).getThis();
}
- 字段增加能力内测中,不过暂时可以通过增加新类,把字段放到新类中的方式来实现字段增加能力
- 新增的类支持包括静态内部类和非内部类
- 对于只有字段访问的函数无法直接修复,可通过调用处间接修复
- 构造方法的修复内测中
- 资源和so的修复内测中
参考文章:
Tinker wiki
Robust wiki
Android热修复原理
Android热更新方案Robust
美团Robust原理解析
网友评论