第一部分:什么是热修复
我们经常上线一个app后,才发现有个bug还没改掉,需要紧急修复。如果按照通常做法,那就是程序猿加班搞定bug,然后测试,重新打包并发布。这样是不是太麻烦了,我相信有过这个经历的人肯定不在少数。热修复就是解决这个问题的。简单点说就是给你的应用打补丁,也就是说你只要把要修改的地方打成补丁放在服务端,通过事先设定好的接口,把补丁下载到客服端,从而修复bug。
第二部分:热修复的原理
热修复技术主要主要是集中在三个地方,代码热修复、资源热修复、So库的热修复,我们平时有bug或是说希望替换的地方也就是这三个方面了。下面从这三个方面来说下热修复原理。
一、代码热修复技术。
代码修复有两大主要方案,一种是阿里系的底层替换方案,另一种是腾讯系的类加载方案。
这两类方案各有优劣:
•底层替换方案限制颇多,但时效性最好,加载轻快,立即见效。
•类加载方案时效性差,需要重新冷启动才能见效,但修复范围广,限制少。
1、底层替换方案
底层替换方案是在已经加载了的类中直接替换掉原有方法,是在原来类的基础上进行修改的。因而无法实现对与原有类进行方法和字段的增减,因为这样将破坏原有类的结构。
一旦补丁类中出现了方法的增加和减少,就会导致这个类以及整个Dex的方法数的变化。方法数的变化伴随着方法索引的变化,这样在访问方法时就无法正常地索引到正确的方法了。如果字段发生了增加和减少,和方法变化的情况一样,所有字段的索引都会发生变化。并且更严重的问题是,如果在程序运行中间某个类突然增加了一个字段,那么对于原先已经产生的这个类的实例,它们还是原来的结构,这是无法改变的。而新方法使用到这些老的实例对象时,访问新增字段就会产生不可预期的结果。
这是这类方案的固有限制,而底层替换方案最为人诟病的地方,在于底层替换的
不稳定性。
传统的底层替换方式,不论是Dexposed、Andfix或者其他安全界的Hook方案,都是直接依赖修改虚拟机方法实体的具体字段。例如,改Dalvik方法的jni函数指针、改类或方法的访问权限等等。这样就带来一个很严重的问题,由于Android是开源的,各个手机厂商都可以对代码进行改造,而Andfix ArtMethod的结构是根据公开的Android源码中的结构写死的。如果某个厂商对这个ArtMethod结构体进行了修改,就和原先开源代码里的结构不一致,那么在这个修改过了的设备上,通用性的替换机制就会出问题。这便是不稳定的根源。
2、类加载方案
类加载方案的原理是在app重新启动后让Classloader去加载新的类。因为在app运行到一半的时候,所有需要发生变更的类已经被加载过了,在Android上是无法对一个类进行卸载的。如果不重启,原来的类还在虚拟机中,就无法加载新类。因此,只有在下次重启的时候,在还没走到业务逻辑之前抢先加载补丁中的新类,这样后续访问这个类时,就会Resolve为新类。从而达到热修复的目的。
再来看看腾讯系三大类加载方案的实现原理。QQ空间方案会侵入打包流程,并且为了 hack添加一些无用的信息,实现起来很不优雅。而QRx的方案,需要获取底层虚拟机的函数,不够稳定可靠,并且有个比较大的问题是无法新增public函数。
微信的Tinke「方案是完整的全量dex加载,并且可谓是将补丁合成做到了极
致,然而我们发现,精密的武器并非适用于所有战场。Tinke的合成方案,是从dex的方法和指令维度进行全量合成,整个过程都是自己研发的。虽然可以很大地节省空间,但由于对dex内容的比较粒度过细,实现较为复杂,性能消耗比较严重。实际上,dex的大小占整个apk的比例是比较低的,一个app里面的dex文件大小并不是主要部分,而占空间大的主要还是资源文件。因此,Tinker的性价比不高。
二、资源热修复技术。
资源的热修复其实是利用下发的补丁包,直接更新资源。大部分现在更新资源的如修复都参考了Instant Run的实现,那么我们简单说下其实现。
基本上来看,分了俩步。
1、构造一个新的AssetManager,并通过反射调用addAssetPath,然后把这个完整的新资源包加入到AssetManager中,这样就得到了一个含新资源的AssetManager。
2、找到之前所有引用到AssetManager的地方,通过反射把引用处替换成AssetManager。
现在资源热修复的技术,大多参考了Instant Run 的实现,有以下几种方案:
1、运行时合成完整的资源包。(比如微信Tinker)
2、下发完整包。(Amigo)
3、修改aapt。
4、构造了一个package id为0x66的资源包,这个包里只包含改
变了的资源项,然后直接在原有AssetManager中addAssetPath这个包。
三、so库热修复技术。
这个一般改动不是很大,热修复一般用不上。
第三部分:热修复现有解决方案
一、AndFix
阿里的热修复解决方案AndFix:https://github.com/alibaba/AndFix,这个框架使用非常简单,下面的使用方法,其实是从文档中翻译过来的,也可以直接看文档。
可以在gradle.build里直接依赖
dependencies {
compile ‘com.alipay.euler:andfix:0.5.0@aar’
}
1、Initialize PatchManager
patchManager = new PatchManager(context);
patchManager.init(appversion);//current version
2、Load patch
patchManager.loadPatch();
这个一般在Application.onCreate()中初始化
3、Add patch
patchManager.addPatch(path);//path of the patch file that was downloaded
下面是工程中的例子,就是按上面的步骤来的。
public class MainApplication extends Application {
private static final String TAG = "euler";
private static final String APATCH_PATH = "/out.apatch";
/**
* patch manager
*/
private PatchManager mPatchManager;
@Override
public void onCreate() {
super.onCreate();
// initialize
mPatchManager = new PatchManager(this);
mPatchManager.init("1.0");
Log.d(TAG, "inited.");
// load patch
mPatchManager.loadPatch();
Log.d(TAG, "apatch loaded.");
// add patch at runtime
try {
// .apatch file path
String patchFileString = Environment.getExternalStorageDirectory()
.getAbsolutePath() + APATCH_PATH;
mPatchManager.addPatch(patchFileString);
Log.d(TAG, "apatch:" + patchFileString + " added.");
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
}
现实中应用out.patch 一般来自网络,下到本地,再去修复。那么这个修复补丁是怎么来的呢?
首先生成修复后的 fixed.apk,要和要修复的bug.apk(就是已经发出去的有错的)用同一个签名key,这个是肯定的,要不被人随便替换了。然后解压开源文件tools目录里的apkpatch-1.0.3工具。
把之前生成的bug.apk和fixed.apk,还有打包所使用的keystore文件放到apkpatch-1.0.3目录下
打开命令行,进入到apkpatch-1.0.3目录下,输入如下指令(Mac的操作)
apkpatch.sh -f fixed.apk -t bug.apk -o patchfile -k keystore -p 111111 -a 111111 -e 111111
每个参数含义如下
-f 新版本的apk
-t 旧版本的apk
-o 输出apatch文件的文件夹,可以随意命名
-k 打包的keystore文件名
-p keystore的密码
-a keystore 用户别名
-e keystore 用户别名的密码
如果出现add modified …….就表示成功了,去apkpatch-1.0.3目录看下,找到以.aptch 为结尾的文件,你可以随便从新命名。
接下来就可以尝试热修复了,先安装bug.apk,然后push .aptch 文件到相应的加载目录(实际中放在后台去),然后重启apk,就可以了。