By吴思博
一、什么是热修复:
二、技术背景
三、当前主流的热修复方案比较
四、Andfix的介绍与实践(开源)
1原理
2基本使用
五、其他方案介绍
一、什么是热修复:
热修复(HotFix)是以补丁的方式动态修复紧急Bug,不再需要重新发布App,不需要用户重新下载覆盖安装的方式来实现代码的替换修改。
二、技术背景
(1)正常开发流程
从流程来看,传统的开发流程存在很多弊端:
1、重新发布版本代价太大
2、用户下载安装成本太高
3、BUG修复不及时,用户体验太差
(2)热修复开发流程
而热修复的开发流程显得更加灵活,优势很多:
1、无需重新发版,实时高效修复
2、用户无感知修复,无需下载新的应用,代价小
3、修复成功率高,把损失降到最低
三、当前主流的热修复方案比较:
热修复技术近期变得越来越热门,同时也出现了一些不同的解决方案,如阿里的AndFix(开源)、QQ空间补丁方案、以及微信的Tinker方案,但是它们的原理、适用场景都各有不同。我们项目中可以采用哪种方案,是我们比较关注的问题。
QQ空间超级补丁技术和微信Tinker支持新增类和资源的替换,在一些功能化的更新上更为强大,但对应用的性能和稳定会有的一定的影响;AndFix虽然暂时不支持新增类和资源的替换,对新功能的发布也有所限制,作为定位为线上紧急BUG的热修复的服务来说,还是比较好的,同时对应用性能不产生不必要的损耗,在热修复方面不失为一个好的选择。
四、Andfix的介绍与实践(定位:一个低成本快速接入的热修复第一方案)
Github:https://github.com/alibaba/AndFix
(1)AndFix是一个Android App的在线热补丁框架。使用此框架,我们能够在不重复发版的情况下,在线修改App中的Bug。AndFix就是“android Hot-Fix”的缩写。就目前来说,AndFix支持Android 2.3到7.0版本,并且支持arm与X86系统架构的设备。支持Dalvik与ART的Runtime。AndFix的补丁文件是以.apatch结尾的文件。
AndFix不同于QQ空间超级补丁技术和微信Tinker通过增加或替换整个DEX的方案,提供了一种运行时在Native修改Filed指针的方式,实现方法的替换,达到即时生效无需重启,对应用无性能消耗
AndFix实现过程:
对于实现方法的替换,在Native层操作,经过三个步骤:
下面以Dalvik设备为例,来分析具体的实现过程
对于Dalvik来说,遵循JIT即时编译机制,需要在运行时装载libdvm.so动态库,获取以下内部函数:
1 dvmThreadSelf( ):查询当前的线程;
2 dvmDecodeIndirectRef():根据当前线程获得ClassObject对象。
setFieldFlag
动态库会忽略非public属性的字段和方法,该操作的目的:让private、protected的方法和字段可被动态库看见并识别。
replaceMethod:
AndFix对ART设备同样支持,具体的过程与Dalvik相似。
该步骤是方法替换的核心,替换的流程如下:
优点:
1、BUG修复的即时
2、补丁包同样采用差量技术,生成的PATCH体积小
3、对应用无侵入,几乎无性能损耗
不足:
1、不支持新增字段,以及修改方法,也不支持对资源的替换。
2、由于厂商的自定义ROM,对少数机型可能暂时不支持。
(2)Andfix的基本使用。
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();
//初始化patch管理类
mPatchManager = new PatchManager(this);
//初始化patch版本
mPatchManager.init("1.0");
//加载已经添加到PatchManager中的patch
mPatchManager.loadPatch();
}
}
2.如果有新的补丁需要修复,下载完成后,进行以下操作
//添加patch,只需指定patch的路径即可,补丁会立即生效
mPatchManager.addPatch(path);
3.当apk版本升级,需要把之前patch文件的删除,需要以下操作
//删除所有已加载的patch文件
mPatchManager.removeAllPatch();
我们也可封装成一个工具类
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 :新版本
-t :旧版本
-o :输出目录
-k :打包所用的keystore
-p :keystore的密码
-a :keystore用户别名
-e :keystore用户别名密码
执行完命令,就会在输出目录中输出.apatch文件:
new-c293df7dbc23f11214fdd020ea78d3b8.apatch
:就是patch文件。
.apatch文件根目录内容:
META_INF
文件下内容:
PATCH.MF
文件内容:
注:Patch-Classes就是改动过的class.
客户端请求服务器接口(api),服务器根据用户传递的数据分析是否有需要修复的bug。如果有bug需要修复,就下载服务器指定的.apatch文件的链接,下载完后及时加载并修复,使用addpatch(path)方法,补丁会立即生效。
dependencies {
compile 'com.alipay.euler:andfix:0.3.1@aar'
}
-keep class * extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {
native ;
}
Q & A:
1、如何解决某些机型不兼容的问题?还有采用YunOS系统的?之前测试在ART模式机型上,偶现崩溃,不知采用了什么机制处理的?线上采用Andfix,会不会风险比较高?
ART的机型上暂时未出现崩溃的现象。对于机型的兼容性问题,采取的措施是做平台区间控制基本兼容,对于个别机型的问题,可以参考统计数据加入黑名单,与服务器约定协议,黑名单上的机型不走andifx流程。美聊线上实践暂时未出现不可控的问题
2、没有成功更新的情况下,会不会引起app的崩溃?
更新不成功不会引起崩溃
五、其他技术简介
android的类加载器分为两种,PathClassLoader和DexClassLoader,两者都继承自BaseDexClassLoader
PathClassLoader用来加载系统类和应用类。DexClassLoader用来加载jar、apk、dex文件.加载jar、apk也是最终抽取里面的Dex文件进行加载。
1、QQ空间超级补丁技术
QQ空间超级补丁技术基于DEX分包方案,使用了多DEX加载的原理,大致的过程就是:把BUG方法修复以后,放到一个单独的DEX里,插入到dexElements数组的最前面,让虚拟机去加载修复完后的方法。
当patch.dex中包含A.class时就会优先加载,在后续的DEX中遇到A.class的话就会直接返回而不去加载,这样就达到了修复的目的。
但是有一个问题是,当两个调用关系的类在同一个DEX时,就会产生异常报错。在APK安装时,虚拟机需要将classes.dex优化成odex文件,然后才会执行。在这个过程中,会进行类的verify操作,如果方法中直接引用到的类(第一层级关系,不会进行递归搜索)和class都在同一个dex中的话,那么这个类就会被打上CLASS_ISPREVERIFIED,然后才会写入odex文件。所以,为了可以正常的进行打补丁修复,必须避免类被打上CLASS_ISPREVERIFIED标志,具体的做法就是单独放一个类在另外DEX中,让其他类调用。
其实就是两件事:
1、动态改变BaseDexClassLoader对象间接引用的dexElements;
2、在app打包的时候,阻止相关类去打上CLASS_ISPREVERIFIED标志。
手机QQ空间APK具体的实现:先进入程序入口QZoneRealApplication,在attachBaseContext中进行了两步操作:(1)修复CLASS_ISPREVERIFIED标志导致的unexpected DEX problem异常。(2)加载修复的DEX。
整体的流程图如下:
从流程图来看,可以很明显的找到这种方式的特点:
优势:
1、没有合成整包(和微信Tinker比起来),产物比较小,比较灵活
2、可以实现类替换,兼容性高。(某些三星手机不起作用)
不足:
1.不支持即时生效,必须通过重启才能生效。
2.为了实现修复这个过程,必须在应用中加入两个dex!dalvikhack.dex中只有一个类,对性能影响不大,但是对于patch.dex来说,修复的类到了一定数量,就需要花不少的时间加载。
3.在ART模式下,如果类修改了结构,就会出现内存错乱的问题。为了解决这个问题,就必须把所有相关的调用类、父类子类等等全部加载到patch.dex中,导致补丁包异常的大,进一步增加应用启动加载的时候,耗时更加严重。
2微信Tinker
微信针对QQ空间超级补丁技术的不足提出了一个提供DEX差量包,整体替换DEX的方案。主要的原理是与QQ空间超级补丁技术基本相同,区别在于不再将patch.dex增加到elements数组中,而是差量的方式给出patch.dex,然后将patch.dex与应用的classes.dex合并,然后整体替换掉旧的DEX,达到修复的目的。
从流程图来看,同样可以很明显的找到这种方式的特点:
优势:
1、合成整包,不用在构造函数插入代码,防止verify,verify和opt在编译期间就已经完成,不会在运行期间进行、
2、性能提高。兼容性和稳定性比较高。
3、开发者透明,不需要对包进行额外处理。
不足:
1、与超级补丁技术一样,不支持即时生效,必须通过重启应用的方式才能生效。
2、需要给应用开启新的进程才能进行合并,并且很容易因为内存消耗等原因合并失败。
3、合并时占用额外磁盘空间,对于多DEX的应用来说,如果修改了多个DEX文件,就需要下发多个patch.dex与对应的classes.dex进行合并操作时这种情况会更严重,因此合并过程的失败率也会更高。
小结
QQ空间超级补丁技术和微信Tinker的修复原理都基于类加载,在功能上已经支持类、资源的替换和新增,功能非常强大。但是也有非常多的问题。
A、多DEX带来的性能问题和影响
多DEX方案用来解决应用方法数65k的问题,现在Google也官方支持了MultiDex的实现方案。但是,这实在是应用因方法数超出而作出的不得已的下策,超级补丁技术和Tinker作为一种热修复的方案,平生给应用增加了多个DEX,而多DEX技术最大的问题在于性能上的坑,因此基于这种方案的补丁技术影响应用的性能是无疑的。
(a)启动加载时间过长
可以看到,超级补丁技术和Tinker都选择在Application的attachBaseContext()进行补丁dex的加载,即使这是加载dex的最佳时机,但是依然会带来很大的性能问题,首当其冲的就是启动时间太长。对于补丁DEX来说,应用启动时虚拟机会将patch.dex文件转换成odex文件,这个过程非常耗时。而这个过程,又要求需要在主线程中,以同步的方式执行,否则无法成功进行修复。就DEX的加载时间,大概做了以下的时间测试。
随着patch.dex的增加,在不做任何优化的情况下,启动时间也直线增长。
(b)易造成应用的ANR和Crash
正是尤其多DEX加载导致了启动时间过长,很容易就会引发应用的ANR。我们知道当应用在主线程等待超过5s以后,就会直接导致长时间无响应而退出。超级补丁技术为保证ART不出现地址错乱问题,需要将所有关联的类全部加入到补丁中,而微信Tinker采取一种差量包合并加载的方式,都会使要加载的dex体积变得很大。这也很大程度上容易导致ANR情况的出现。
除了应用ANR以外,多DEX模式也同样很容易导致Crash情况的出现。我们知道,超级补丁技术为了保证ART设备下不出现地址错乱问题,需要把修改类的所有相关类全部加入到补丁中,这里会出现一个问题,为了保证补丁包的体积最小,能否保证引入全部的关联类而不引入无关的类呢?一旦没有引入关联的类,就会出现以下的异常:
NoClassDefFoundError
Could not find class
Could not find method
出现这些异常,就会直接导致应用的Crash退出。所以,不难看出如果我们需要修复一个不是Crash的BUG,但是因为未加入相关类而导致了更严重的Crash,就更加的得不偿失。
如果我们仅仅就是开发一款app,没有大改动,不会热更全局变量,不会增加方法,那么AndFix框架是首选。