当前市面的热补丁方案有很多,其中比较出名的有阿里的HotFix、美团的Robust、微信的Tinker以及QZone的超级补丁方案。

1、热修复技术的优势?

  • 无需重新发版,实时高效热修复
  • 用户无感知修复,无需下载新的应用,代价小
  • 远程调试
平台 阿里百川HotFix(Sophix) AndFix Tinker Qzone Robust
即时生效 yes yes no no yes
性能损耗 较小 较小 较大 较大 较小
侵入式打包 无侵入式打包 无侵入式打包 依赖侵入式打包 依赖侵入式打包 依赖侵入式打包
Rom体积 较小 较小 较大 较小 较小
接入复杂度 傻瓜式接入 比较简单 复杂 比较简单 复杂
补丁包大小 较小 较小 较小 较大 一般
全平台支持 yes yes yes yes yes
类替换 yes yes yes yes no
so替换 yes no yes no no
资源替换 yes no yes yes no
成功率 ? 一般 较高(95.6%) 较高 最高(99.9%)

可以看到阿里的Sophix有很大优势,阿里系在热修复领域有很多积累,我们可以看下阿里系的热修复技术发展路径,一张表格来说明一下各个版本热修复的差别:

方案对比 Andfix开源版本 阿里Hotfix 1.X 阿里Hotfix 最新版 (Sophix)
方法替换 支持,除部分情况[0] 支持,除部分情况 全部支持
方法增加减少 不支持 不支持 以冷启动方式支持[1]
方法反射调用 只支持静态方法 只支持静态方法 以冷启动方式支持
即时生效 支持 支持 视情况支持[2]
多DEX 不支持 支持 支持
资源更新 不支持 不支持 支持
so库更新 不支持 不支持 支持
Android版本 支持2.3~7.0 支持2.3~6.0 全部支持包含7.0以上
已有机型 大部分支持[3] 大部分支持 全部支持
安全机制 加密传输及签名校验 加密传输及签名校验
性能损耗 低,几乎无损耗 低,几乎无损耗 低,仅冷启动情况下有些损耗
生成补丁 繁琐,命令行操作 繁琐,命令行操作 便捷,图形化界面
补丁大小 不大,仅变动的类 小,仅变动的方法 不大,仅变动的资源和代码[4]
服务端支持 支持服务端控制[5] 支持服务端控制

说明: [0] 部分情况指的是构造方法、参数数目大于8或者参数包括long,double,float基本类型的方法。 [1] 冷启动方式,指的是需要重启app在下次启动时才能生效。 [2] 对于Andfix及Hotfix 1.X能够支持的代码变动情况,都能做到即时生效。而对于其他代码变动较大的情况,会走冷启动方式,此时就无法做到即时生效。 [3] Hotfix 1.X已经支持绝大部分主流手机,只是在X86设备以及修改了虚拟机底层结构的ROM上不支持。 [4] 由于支持了资源和库,如果有这些方面的更新,就会导致的补丁变大一些,这个是很正常的。并且由于只包含差异的部分,所以补丁已经是最大程度的小了。 [5] 提供服务端的补丁发布和停发、版本控制和灰度功能,存储开发者上传的补丁包。

Sophix目前最新版本3.0,收费服务。

2、主流方案原理分析

Robust

原理 :Robust插件对每个产品代码的每个函数都在编译打包阶段自动的插入了一段代码,插入过程对业务开发是完全透明。

Robust为每个class增加了个类型为ChangeQuickRedirect的静态成员,而在每个方法前都插入了使用changeQuickRedirect相关的逻辑,当 changeQuickRedirect不为null时,可能会执行到accessDispatch从而替换掉之前老的逻辑,达到fix的目的。

如State.java的getIndex函数:

public longgetIndex(){

    **return** 100;

}

被处理成如下的实现:

public static ChangeQuickRedirectchangeQuickRedirect;

    public longgetIndex(){

        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;

 }

简述 :客户端拿到含有PatchesInfoImpl.java和StatePatch.java的patch.dex后,用DexClassLoader加载patch.dex,反射拿到PatchesInfoImpl.java这个class。拿到后,创建这个class的一个对象。然后通过这个对象的getPatchedClassesInfo函数,知道需要patch的class为xxx,再反射得到当前运行环境中的xxx class,将其中的changeQuickRedirect字段赋值为用patch.dex中的StatePatch.java这个class new出来的对象。这就是打patch的主要过程。通过原理分析,其实Robust只是在正常的使用DexClassLoader,所以可以说这套框架是没有兼容性问题的。

AndFix

原理 :AndFix的原理是在加载补丁文件后,通过Native层使用指针替换的方式将老方法Method对象的方法指针替换成补丁包中新方法的,从而达到修复bug的目的。由于它并没有整体替换class, 而field在class中的相对地址在class加载时已确定,所以AndFix无法支持新增或者删除filed的情况(通过替换init与clinit值可以修改field的数值)。

优点:即时生效,无性能损耗,无侵入。缺点:不能新增字段以及修改<init>方法,不能修改资源,兼容性可能有问题。

简述 :AndFix采用native hook的方式,这套方案直接使用dalvik_replaceMethod替换class中方法的实现。

我们看下主要的几个natvie方法

public class AndFix {

    //运行时装载libdvm.so动态库,用于获取内部函数1、dvmThreadSelf():查询当前的线程,2、dvmDecodeIndirectRef():根据当前线程获得ClassObject对象

    private static native booleansetup(booleanisArt,intapilevel);

    //让private、protected的方法和字段可被动态库看见并识别。原因在于动态库会忽略非public属性的字段和方法。

    private static native voidsetFieldFlag(Fieldfield);

    //指针替换

    private static native voidreplaceMethod(Methoddest,Methodsrc);

}

这个几个方法对应的操作

重点分析下replaceMethod 方法,这此之前,我们需要穿插一个知识点。

Android 中的Dalvik和ART是什么,有啥区别?

Android Runtime(缩写为ART),是一种在Android操作系统上的运行环境,由Google公司研发,并在2013年作为Android 4.4系统中的一项测试功能正式对外发布,在Android 5.0及后续Android版本中作为正式的运行时库取代了以往的Dalvik虚拟机。ART能够把应用程序的字节码转换为机器码,是Android所使用的一种新的虚拟机。它与Dalvik的主要不同在于:Dalvik采用的是JIT技术,而ART采用Ahead-of-time(AOT)技术。ART同时也改善了性能、垃圾回收(Garbage Collection)、应用程序除错以及性能分析。 JIT最早在 Android 2.2 系统中引进到Dalvik虚拟机中,在应用程序启动时,JIT通过进行连续的 性能分析 来优化程序代码的执行,在程序运行的过程中,Dalvik虚拟机在不断的进行将字节码编译成机器码的工作。与Dalvik虚拟机不同的是,ART引入了AOT这种预编译技术,在应用程序安装的过程中,ART就已经将所有的字节码重新编译成了机器码。应用程序运行过程中无需进行实时的编译工作,只需要进行直接调用。因此,ART极大的提高了应用程序的运行效率,同时也减少了手机的电量消耗,提高了移动设备的续航能力,在垃圾回收等机制上也有了较大的提升。

通过源码结构,我们可以看出AndFix针对不同的运行环境做了不同的处理。

dalvik_replaceMethod方法实现:

externvoid\_\_attribute\_\_((visibility(&quot;hidden&quot;)))dalvik\_replaceMethod(

    JNIEnv\*env,jobjectsrc,jobjectdest){

        _//找到被替换的类_

        jobjectclazz=env-&gt;CallObjectMethod(dest,jClassMethod);

       _//通过类找到类的对象_

        ClassObject\*clz=(ClassObject\*)dvmDecodeIndirectRef\_fnPtr(

                        dvmThreadSelf\_fnPtr(),clazz);

       _//将类的状态初始化_

        clz-&gt;status=CLASS\_INITIALIZED;

       _//找到新方法的指针_

        Method\*meth=(Method\*)env-&gt;FromReflectedMethod(src);

       _//找到需要修复的旧方法指针_

        Method\*target=(Method\*)env-&gt;FromReflectedMethod(dest);

        LOGD(&quot;dalvikMethod: %s&quot;,meth-&gt;name);

_//        meth-&gt;clazz = target-&gt;clazz;_

       _//访问属性替换为public_

        meth-&gt;accessFlags|=ACC\_PUBLIC;

        meth-&gt;methodIndex=target-&gt;methodIndex;

        meth-&gt;jniArgInfo=target-&gt;jniArgInfo;

        meth-&gt;registersSize=target-&gt;registersSize;

        meth-&gt;outsSize=target-&gt;outsSize;

        meth-&gt;insSize=target-&gt;insSize;

       _//方法指针替换_

        meth-&gt;prototype=target-&gt;prototype;

        meth-&gt;insns=target-&gt;insns;

        meth-&gt;nativeFunc=target-&gt;nativeFunc;

}

ART的具体实现不同,但基本流程一致。

# QZone

原理 :QZone方案并没有开源。