关于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需要修改所有classclass_ispreverified标志位,导致运行时性能有所损失)


2.
支持ARTDalvik

3.
支持6.0

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

在线修复bug

【实现原理】

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



【源码下载】

https://github.com/alibaba/AndFix

Nuwa

(HotFix/DroidFix)

 

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

2.支持ARTDalvik



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()中的代码,如下:

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第1张图片


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


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

修改输出代码为如下:

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第2张图片


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

工具所在目录:

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第3张图片

工具使用说明:

ApkPatch v1.0.3 - a tool for build/merge Android Patch file (.apatch).
Copyright 2015 supern lee 

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

usage: apkpatch -m  -k  -p <***> -a  -e <***>
 -a,--alias      alias.
 -e,--epassword <***>   entry password.
 -k,--keystore     keystore path.
 -m,--merge     path of .apatch files.
 -n,--name        patch name.
 -o,--out          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个,会在后面进行解释:

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第4张图片

这里的.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()这个函数完成的。

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第5张图片


进入doPatch函数看看:

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第6张图片


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

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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第7张图片

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


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

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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第8张图片

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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第9张图片


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

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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第10张图片


总结:

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


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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第11张图片


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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第12张图片



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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第13张图片


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



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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第14张图片


PatchBuilder的代码如下:

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第15张图片


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

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

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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第16张图片


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


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

最终的产物如下:

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第17张图片


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

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

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


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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第18张图片


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

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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第19张图片


initPatchs()代码如下:

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第20张图片

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

这是mPatchDir的初始值:

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第21张图片


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

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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第22张图片


fix()函数的代码如下:

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第23张图片


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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第24张图片

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

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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第25张图片


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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第26张图片


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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第27张图片


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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第28张图片


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

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

关于Android APP在线热修复bug方案的调研(一)(AndFix)_第29张图片



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

很犀利的做法。


【总结】

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

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

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

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

你可能感兴趣的:(Android)