热修复是在应用的App包发布到市场之后,出现了Bug,无需替换包来进行在线更新的一种技术,对用户是无感知的。目前广义上有两种方案可以实现代码的替换,一种是类的替换,基于Classloader;另一种是方法的替换,而这两种方式各有优缺点。
- 方法的替换:只能替换方法的内容,所以不能够对要patch的类进行方法的新增和删除;但同时,方法的替换可以在应用不重启的情况下实现。它包小、快速、功能单一、比较轻量,这种方案是热修复。
- 类的替换:可以修改类结构,功能更加的强大;但是必须要重启一次才会有效,因为已经加载过的类是不能够被替换的。这种方案叫做“动态部署”。它几乎能够适应任何代码的变更,所以很适合进行业务功能的迭代。
-
方法的替换
如图所示,方法的替换的原理如下:在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是更好的选择。