背景
当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布。
这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装?
答案是:可以的
解决方案
1、Xposed
先来了解一下Xposed:诞生于XDA论坛,类似一个应用平台,不同的是其提供诸多系统级的应用。可实现许多神奇的功能。Xposed需要以越狱为前提,也就是说需要root权限,想了解详细,可以自行查阅相关资料,由于需要root权限,但我们的APP对于用户的手机来说,不太可能人人都root,用户也不会自行去安装这个工具,所以此方案对我们APP来说不合适。
2、Dexposed
DexPosed是强大而无侵入的AOP(面向切面编程)运行Android应用程序开发框架,基于开源的Xposed框架项目的工作(Xposed是修改系统框架服务的框架),拥有强大的:「原方法前hook」「方法替换」「原方法后hook」三种方式。相互组合,可依据你的hook思想,解决和规避几乎所有的意外情形。它更像是一个hook工具,效果跟使用者思路关系紧密,是地地道道的hook方案。在阿里有着良好的实践成果。
此方案的确可以解决BUG的热修复问题,但有很大的弊端,就是系统兼容性问题:
支持的系统
也就是说对于Android5.0系统之后,我们的热修复还是失败的,那么有木有一种方案是,不需要root,而且平台范围支持广的方案呢?那就是我们今天要重点说的AndFix方案了。
3、AndFix
AndFix,全称是Android hot-fix。是阿里开源的一个热补丁框架,允许APP在不重新发布版本的情况下修复线上的bug。支持Android 2.3 到 6.0,并且支持arm 与 X86系统架构的设备。完美支持Dalvik与ART的Runtime,补丁文件是以 .apatch 结尾的文件。
AndFix原理
AndFix的原理就是方法的替换,把有bug的方法替换成补丁文件中的方法。
注:在Native层使用指针替换的方式替换bug方法,已达到修复bug的目的。
使用AndFix修复热修复的整体流程:
方法替换过程:
Android上如何使用
1.在自定义Application中初始化,为了更早的修复应用中的bug。
2.如果有新的补丁需要修复,下载完成后,进行以下操作:
//添加patch,只需指定patch的路径即可,补丁会立即生效
mPatchManager.addPatch(path);
3.当apk版本升级,需要把之前patch文件的删除,需要以下操作:
mPatchManager.removeAllPatch();
使用工具:apkpatch-1.0.3
它根据两个apk差别来生成apatch文件,不要将其完全理解成是一个差异文件。它是对比两个apk中的smali文件,找到不同的方法,增加方法annotation(供客户端修复逻辑识别并修复),保留此方法所在类的smali描述,修改类名、将其再打成dex,并与META-INF下的签名、证书、包含有patch信息的PATCH.MF一并打成一个压缩文件,文件格式命为apatch。
对smali类方法修改的内容如下:
.class public Lcom/open/andfixdemo/MainActivity_CF;
.super Landroid/app/Activity;
.source "MainActivity.java"
.method public showResultAsTextView()V
.locals 2
.annotation runtime Lcom/alipay/euler/andfix/annotation/MethodReplace;
clazz = "com.open.andfixdemo.MainActivity"
method = "showResultAsTextView"
.end annotation
.prologue
.line 42
const v1, 0x7f080002
invoke-virtual {p0, v1}, Lcom/open/andfixdemo/MainActivity_CF;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/TextView;
.line 43
.local v0, "textView":Landroid/widget/TextView;
const-string v1, "4"
invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
.line 44
return-void
.end method
.method public showResultAsToast()V
.locals 2
.annotation runtime Lcom/alipay/euler/andfix/annotation/MethodReplace;
clazz = "com.open.andfixdemo.MainActivity"
method = "showResultAsToast"
.end annotation
.prologue
.line 38
const-string v0, "2"
const/4 v1, 0x1
invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
.line 39
return-void
.end method
请留意这些修改:
类名由MainActivity被修改成为MainActivity_CF。
被替换的方法showResultAsTextView、showResultAsToast被添加了一个MethodReplace注解。注解描述中说明了原类及原方法。
由此,你应该了解到,对于客户端,这些信息已经足够。那么客户端的处理为:解析apatch->解析dex->加载类->识别含有MethodReplace注解的方法->根据原方法签名已经新方法smali描述进行hook并替换。
命令:
apkpatch.bat-f new.apk-t old.apk-o output-k debug.keystore-p android-a androiddebugkey-e android
-f:新版本
-t: 旧版本
-o: 输出目录
-k: 打包所用的keystore
-p: keystore的密码
-a: keystore 用户别名
-e: keystore 用户别名密码
注:debug.keystore我用的是.android目录下默认签名工具,需要先把debug.keystore拷到apkpatch-1.0.3根目录下
执行完命令,就会在输出目录中输出.apatch文件
.apatch文件根目录内容:
META_INF文件下内容:
PATCH.MF文件内容:
diff.dex文件反编译后的结果:
在Android Studio使用
gradle dependency:
dependencies{
compile 'com.alipay.euler:andfix:0.3.1@aar'
}
在Eclipse使用
把Java层的代码引入到你的工程,配置Ndk开发环境并把jni Native代码添加进来。
代码混淆(ProGuard)
-keep class *extendsjava.lang.annotation.Annotation
-keep classeswithmembernamesclass* {
native <methods>;
}
其他方案
DexPosed和AndFix都属于修改Java类的C层对象来实现实现热修复,QQ空间的nuwa方案是通过修改BaseDexClassLoader中的pathList,来动态加载dex方式实现热修复。后者纯java实现,但需要hack类的优化流程,将打CLASS_ISPREVERIFIED标签的类,去除此标签,以解决类与类引用不在一个dex中的异常问题。这会放弃dex optimize对启动运行速度的优化。原则上,这对于方法数没有大到需要multidex的应用,损失更明显。
问题:
1、线上分次发布多patch
2、拿不到签名工具
参考资料:
4、Alibaba-Dexposed框架在线热补丁修复的使用 - Coolspan - 博客频道 - CSDN.NET
5、Android-FixBug热修复框架的使用及源码分析(不发版修复bug) - Coolspan - 博客频道 - CSDN.NET