热修复是在应用的App包发布到市场之后,出现了Bug,无需替换包来进行在线更新的一种技术,对用户是无感知的。目前广义上有两种方案可以实现代码的替换,一种是类的替换,基于Classloader;另一种是方法的替换,而这两种方式各有优缺点。

  1. 方法的替换:只能替换方法的内容,所以不能够对要patch的类进行方法的新增和删除;但同时,方法的替换可以在应用不重启的情况下实现。它包小、快速、功能单一、比较轻量,这种方案是热修复
  2. 类的替换:可以修改类结构,功能更加的强大;但是必须要重启一次才会有效,因为已经加载过的类是不能够被替换的。这种方案叫做“动态部署”。它几乎能够适应任何代码的变更,所以很适合进行业务功能的迭代。
  • 方法的替换

  如图所示,方法的替换的原理如下:在Android底层,有个数据结构记录着类的信息,比如成员变量的个数,方法的个数,每个方法的code执行地址,程序运行的时候会根据这个地址跳转到具体的code区域执行代码。方法的替换就是替换这些地址,把地址指向另一个类的方法,从而达到了替换的目的。这种方式的包比较小,而且也不需要插桩来影响性能,但是它无法修改类的结构(比如方法数的增删),而且有可能会有兼容性问题,例如厂商修改了类和方法的底层结构。

 Andfix

  Andfix是阿里巴巴开源项目。AndFix就是 “Android Hot-Fix”的缩写,一个Android App的在线热补丁框架。使用此框架,我们能够在不重复发版的情况下,在线修改App中的Bug。就目前来说,AndFix支持Android 2.3到6.0版本,并且支持arm 与 X86系统架构的设备。完美支持Dalvik与ART的Runtime。

  AndFix 的补丁文件是以.apatch结尾的文件。Andfix的修复是方法级的,对有bug的方法进行替换。

  使用这个框架的局限在于不能修改全局变量,不能加新的方法,不过可以在现有的方法上做修改,加局部变量。从这方面来看,Andfix其实要求我们只是修改方法里面的bug,不能大规模做更改。如果我们觉得这种修复不能满足修复要求,那么,可以看另外这种,局限更少的热修方案。

 

实现方法如下:

1. 在自定义Application中初始化,为了更早的修复应用中的bug。

package com.euler.andfix;
import android.app.Application;
import com.alipay.euler.andfix.patch.PatchManager;
public class MainApplication extends Application {
    public PatchManager mPatchManager;
    @Override
    public void onCreate() {
        super.onCreate();
        mPatchManager = new PatchManager(this); // 初始化patch管理类
        mPatchManager.init("1.0"); // 初始化patch版本
        mPatchManager.loadPatch();// 加载已经添加到PatchManager中的patch
    }
}

2. 如果有新的补丁需要修复,下载完成后,进行以下操作

mPatchManager.addPatch(path); //添加patch,只需指定patch的路径即可,补丁会立即生效

 

 3. 当apk版本升级,需要把之前patch文件的删除,需要以下操作

mPatchManager.removeAllPatch();

 

4. patch文件的生成

使用工具:apkpatch-1.0.3

原理:根据两个apk包,生成一个差异文件,就是所谓的补丁文件即patch文件。

命令 : apkpatch.bat -f new.apk -t old.apk -o output1 -k debug.keystore -p android -a androiddebugkey -e android
-f <new.apk> :    新版本
-t <old.apk> :     旧版本
-o <output> :      输出目录
-k <keystore>:     打包所用的keystore
-p <password>:     keystore的密码
-a <alias>:         keystore 用户别名
-e <alias password>: keystore 用户别名密码

执行完命令,就会在输出目录中输出.apatch文件:

  • .apatch文件中包含META-INF和classes.dex两个文件;
  • META_INF中有CERT.RSA,CERT.SF,MANIFEST.MF和PATCH.MF;
  • PATCH.MF的内容如右图所示

  注:Patch-Classes就是改动过的class。

5. 客户端请求服务器接口(api),服务器根据用户传递的数据分析是否有需要修复的bug。 如果有bug需要修复,就下载服务器指定的.apatch文件的链接,下载完后及时加载并修复,使用addpatch(path)方法,补丁会立即生效。

  • 类的替换(如HotFix项目)

  如图所示,APK包中包含了代码和资源,代码存放在classes.dex中,资源存放在APK的res目录下,系统会通过ClassLoader装载classes.dex来进行类加载。如果有多个classes.dex,那么这多个classes.dex会在ClassLoader中会用数组的形式来进行组织,然后从前往后进行遍历查找。

 

  一种具体的方案如图2所示,是利用多dex从前往后遍历的有序特性,把patch.dex插入到数组的最前面,对patch进行有限查找,达到替换的目的,这时候就会出现preverify问题,为了绕过这个问题,就必须往每个类的构造函数里面进行插桩防止安装期间的校验,把校验从编译期间移动到了运行期间,导致运行期间每加载一个类,都要进行校验和优化,降低了运行性能。

 

  这种方案利用了ClassLoader,思想是比较好的,但是如果工程中的类比较多,就会在性能上造成比较大的影响。同时,有可能会导致patch包比较大。

  为了解决上述性能问题,出现了另一种方案,如图3所示:就是全量替换dex,下发一个patch.dex,然后把原来的dex和下发的patch.dex,合成一个新的全量dex,这个dex会全量替换原来的dex,最终本质上也只会在这个全量的dex中查找,从而不会出现preverify问题,所以这种方案不用插入构造函数,不会影响性能。但是,这种方案的包也可能会比较大,只改动了一行代码,也必须合成一个全量的dex,如图4所示,然后再dexopt出一个全量的odex。

 

Hotfix

  该项目是基于QQ空间终端开发团队的技术文章实现的。项目包括核心类库,补丁制作库,例子。该技术的原理是用ClassLoader加载机制,覆盖掉有问题的方法。所以我们的补丁其实就是有问题的类打成的一个包。

如何选择热修复技术方案

  • 热修复技术方案的选择应该对应具体的使用场景。
  • 在patch包的数量、大小方面,热修复(Andfix是占优势的;
  • 但是热修复(Andfix不能新增类、新增字段,从这个角度看,ClassLoader更好,但是会有一定的性能损耗;

合成dex的方式,则需要知道用户手机的ROM有多大,能不能提供20多M的空间来。

  对比两种解决方案,阿里的andfix更注重于改细节的bug,虽然它是从native层做得操作,但是框架封装的很好,我们使用起来很简便,而且有更新维护,据说阿里系的app打算都用这个。如果我们仅仅就是开发一款app,还没有大改动,不会热更全局变量,不会增加方法,那么这个框架就是首选。

  但是有的时候我们可能开发的是一款sdk,譬如友盟sdk之类,或者想热更全局变量,增加方法,那么andfix可以说是用不到的,所以这个时候hotfix是更好的选择。