热修复框架对比(Tinker、Robust)

2019-07-02  本文已影响0人  小懒猴

一、框架特性对比

特性 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中。

bakApk.png

然后修改要修复的代码,生成补丁包前需要修改build.gradle中的三个参数

调用tinkerPatchDebug, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/。然后将补丁包patch_signed_7zip.apk推送到手机的sdcard中就可以修复Bug了。

tinkerPatchTask.png

2、Robust

自动化补丁工具

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

mapping与methodsMap.robust文件.png

在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),如下图:

ClassLoader查找流程.png

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将不为空,执行到方法时就会调用补丁的方法而不是原方法的逻辑,这样就起到了热修复的目的。

补丁加载.png

四、注意事项

1、Tinker

2、Robust

method a(){
  return this;
}

改为

method a(){
  return new B().setThis(this).getThis();
}

参考文章:
Tinker wiki
Robust wiki
Android热修复原理
Android热更新方案Robust
美团Robust原理解析

上一篇 下一篇

猜你喜欢

热点阅读