关于Android APP在线热修复bug方案的调研(一)(AndFix)

调研背景:

     App发布出去后,如果发现有紧急或重要bug如何进行修复呢?

     重新发布一版APK?但这样代价太大....

      那么有没有一种方案能够不用更新整个APK,而只把服务器上的很小的补丁文件下载下来进行修复bug呢?

      本文的调研也正是为了解决该问题。


几种解决方案对比:

      下面是找到的3款比较火的开源解决方案,分别都是利用不同原理实现的:

名称

优点

缺点

适用场景

实现原理与下载

DynamicAPK

1.支持插件化开发,并且Plugin APK能够访问Host APK的资源

1.需要修改aapt工具,改变原来的编译流程。

2.不支持Hot fix(官方介绍有文字提到支持,但只是load的时候考虑了下,还有很多工作没有实现)

 

适用于Plugin开发

【实现原理】

这是携程网开源的,Multidex+AAPT编译流程改造。参考:http://www.infoq.com/cn/articles/ctrip-android-dynamic-loading?utm_campaign=infoq_content&



【源码下载】

https://github.com/CtripMobile/DynamicAPK

 

AndFix

1.与Mexdex方案相比,性能要好些。(Multi Dex需要修改所有class的class_ispreverified标志位,导致运行时性能有所损失)


2.支持ART与Dalvik

3.支持6.0

1.跳过了类初始化,对于静态或者构造函数或者class.forname()的处理可能会有问题

在线修复bug

【实现原理】

阿里的开源项目,原理是函数Hook。GitHub上有介绍。



【源码下载】

https://github.com/alibaba/AndFix

Nuwa

(HotFix/DroidFix)

 

1.兼容性比AndFix好(Multi dex方案,没有static的问题)

2.支持ART与Dalvik



3.支持6.0

 

 1.编译完成java代码后,需要遍历修改class文件,插入代码,防止class被打上class_ispreverified标志,这会导致运行性能降低


 

在线修复bug

 【实现原理】

Multidex的动态加载原理,参考:http://bugly.qq.com/blog/?p=781



【源码下载】
https://github.com/jasonross/Nuwa


 结合我的目标,目标锁定在第2个与第3个。


AndFix的操作使用】

选择第二个AndFix来进行研究:

第一步:模拟发布APK:

使用自带的sample中的列子,修了下onCreate()中的代码,如下:



安装运行bug.apk,如预期输出如下Log:


第二步:现在App发布出去后有Bug啦,我们要改变onCreate()的输出,赶紧开始制作patch.....

修改输出代码为如下:



编译出fix后的APK,这时需要使用AndFix/tools/下面的apkpatch这个工具来制作patch(补丁)文件。

工具所在目录:


工具使用说明:

ApkPatch v1.0.3 - a tool for build/merge Android Patch file (.apatch).
Copyright 2015 supern lee <sanping.li@alipay.com>

usage: apkpatch -f <new> -t <old> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
 -a,--alias <alias>     alias.
 -e,--epassword <***>   entry password.
 -f,--from <loc>        new Apk file path.
 -k,--keystore <loc>    keystore path.
 -n,--name <name>       patch name.
 -o,--out <dir>         output dir.
 -p,--kpassword <***>   keystore password.
 -t,--to <loc>          old Apk file path.

usage: apkpatch -m <apatch_path...> -k <keystore> -p <***> -a <alias> -e <***>
 -a,--alias <alias>     alias.
 -e,--epassword <***>   entry password.
 -k,--keystore <loc>    keystore path.
 -m,--merge <loc...>    path of .apatch files.
 -n,--name <name>       patch name.
 -o,--out <dir>         output dir.
 -p,--kpassword <***>   keystore password.

下面就开始使用这个命令来制作patch文件:

执行如下命令:
apkpatch.sh -f fixed.apk -t bug.apk -o out -k sig -p 123123 -a test_sig -e 123123

命令的输出:
add modified Method:V  onCreate(Landroid/os/Bundle;)  in Class:Lcom/euler/andfix/MainActivity;


第三步:到此补丁文件制作好了,就可以通过网络等渠道推送到手机上。

查看out目录,一共有3个,会在后面进行解释:


这里的.patch文件就是我们需要的,把它push到手机:



重起刚才的APP,观看Log输出时否改变了:


^_^,补丁成功了,而且这个文件也才3KB多点哦,非常适合在线修复bug~~~~

-rw-rw-r-- 1 yanchen yanchen 3468 12月 15 18:32 out/fixed-07d99a18833f092518fbb041c793e53b.apatch


AndFix的源码分析

============================================================================================================================

Patch的制作流程现在知道了,下一步就准备进入Code层面的分析,探究它的内部实现原理。

============================================================================================================================


首先来看Patch制作原理:

制作patch会用到apkpatch这个工具,而它会调用apkpatch-1.0.3.jar这个文件。反编译这个jar包,发现在制作patch时,它会进入ApkPatch的doPatch()这个函数完成的。


进入doPatch函数看看:



这个函数逻辑挺清晰的,一共就这4个步骤。

第一步是diff,它会比对两个APK的差别,来看看它的代码是如何实现的:


这个函数会提取fixed.apk与bug.apk中的classes.dex文件,然后通过2个for循环,来对比每个class文件中的字段与函数是否完全一致。


那么字段和函数又是如何对比的呢?

我们来看看compareMethod()函数内部实现:


原来对比函数时会看两个是否有函数实现,有的话就会看两个函数的函数体是否一致,代码如下:


如果不一致就会添加到一个叫info的HashSet变量中。

而比对字段主要看字段的初始值是否一样,代码如下:



总结:

通过上面的步骤就比对出了两个APK中的classes.dex中差别,并且将有差别的文件保存在了info变量中。


接下来进入第二步,开始进行buildCode(),这步的目的是将info中保存的文件写到.smali文件中,然后再打包成一个dex文件。


比如下面的是我的demo产生的smali文件:




在生成的smali函数中,它会添加一个自定义的Anotation,叫MethodReplace



到此,第二个步骤也就完成了,它一共产生2个文件:



接下来进入第三个步骤,调用build()函数,这步会将上一步产生的dex文件写入到一个jar文件,并进行签名,代码如下:


PatchBuilder的代码如下:


所以第三步完成后,会产生一个叫作diff.apatch的jar文件。

接下来进入最后一步,调用release()函数,也就是通过它产生最终的patch文件。

看看release()中都做了些什么事情:


主要是将第三步生成的diff.apatch文件重命名为name-md5-.patch格式的文件


到此,patch的制作原理也就告一段落了,主要就是提取两个APK中的classes.dex,对比他们中的class文件是否有区别,将有区别的提取出来打包到.apatch文件中。

最终的产物如下:


============================================================================================================================

以上是Patch的制作流程分析,下一下我们再来看看客户端是如何将这个patch文件打入自己的APK中的。

============================================================================================================================


要使用这套框架,客户端只需在Application的onCreate()中加入如下的代码:


那我们就来看看这几行代码到底会做什么事情。

init()函数主要工作时载入files/apatch/目录下的.apatch文件,代码如下:


initPatchs()代码如下:

它读取mPathDir目录下的所有.patch文件,并将起添加到一个叫mPatchs的HashSet变量中。

这是mPatchDir的初始值:


看来这个函数的目的就是在启动的时候,读取所有本地的patch文件。

读取完毕后调用loadPatch()来进行运行时的APK修复,代码如下:


fix()函数的代码如下:


它会加载.apatch文件中class,然后再调用fixClass(),继续往下看:


会读取自定义的Anotation MethodReplace,通过它获取到class名字与method名字,进行替换。

在前面的分析中也介绍过MethodReplace的内容格式,可参考如下:


而在进行Replace Method是,是在Native层做:


在JNI的代码中,支持Dalvik与ART,这时它的代码结构:


其修复原理就是从内存中找出原来函数指令指针,让它指向新的函数地址:



上面的meth变量便是我们bug.apk中的函数的句柄,target便是.aptach文件中函数的句柄。

而insns是函数指令地址的指针,解释如下:



到此函数替换的原理就水落石出了,就是函数Hook。

很犀利的做法。


【总结】

所以总结一下补丁执行的原理就是:

在运行时,读取patch文件中的函数,将它的函数指令地址赋给APK中的函数。

这样不就等于替换了原来的函数么?那么bug也就可以被消除了。。。

当然这一切都是在内存中进行的,不会对本地的APK有任何影响。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值