在Android中使用Bsdiff实现增量更新
在Android中,我们应用内更新软件通常是下载完整的安装包,然后进行安装。但是当安装包很大的时候,每次更新都会让用户不爽,因为不仅会消耗很多流量,而且当用户网络不是很好的时候,更新就会很慢,而且会影响到用户体验,比如下载期间占用带宽导致加载图片缓慢等。因此,用户很可能会拒绝更新。
bsdiff就是一种差量算法,可以根据两个文件间的区别生成一份差量文件,然后根据旧文件和差量文件重新生成新文件。应用在Android中是这样的:用户安装的是v1.0版本,然后当更新v2.0版本时,服务端根据v1.0和v2.0生成一个差量包patch,然后用户提示更新的时候去下载patch,再在本地根据已安装的版本v1.0和patch合成v2.0版本然后进行安装更新。
编译服务端使用的bsdiff
在服务端,是可以直接安装bsdiff的,但是为了保持bsdiff版本与应用中的版本的一致,因此采用自己编译的方式。
下载源码
首先下载bsdiff的源码:官网地址 ,但是官网下载的时候居然提示403。因此我上传了一份到github上,可以从github下载或者从这里下载。
然后下载bzip2的源码:从SourceForge下载,因为bsdiff需要使用到bzip2。
开始编译
Windows编译是很麻烦的,缺少相应的环境和工具,并且bsdiff中还引用了一些Linux中的头文件。所以这里选择在Linux中编译。
首先解压bsdiff和bzip2,并将二者置于同一个目录中。
1 | |
然后修改bsdiff中的Makefile,因为bsdiff引用了bzip2的头文件和库文件,所以需要将搜索路径指向我们解压后的bzip2-1.0.6。同时,Makefile中还有一些格式问题,同样需要修改。修改后的Makefile如下:
1 | |
改动不是很多,首先加了一个BZIP2PATH参数并指向bzip2的路径,然后在CFLAGS中指定库文件搜索目录-L${BZIP2PATH}和头文件搜索路径-I ${BZIP2PATH}为bzip2路径。其次是指定了编译器为gcc,并且给bsdiff和bspatch添加了明确的生成的命令。最后是在install命令中的.ifndef和.endif前加了个tab缩进。
在CFLAGS中,使用-lbz2链接了bz2库,所以需要先生成libbz2.a。切到bzip2-1.0.6目录中,然后执行命令:
1 | |
此时在bzip2-1.0.6中可以看到生成了libbz2.a文件,然后切回bsdiff-4.3目录中执行命令:
1 | |
这时候,在bsdiff-4.3目录中就会生成bsdiff和bspatch两个可执行文件了。实际上我们是不需要bspatch这个可执行文件的,因为合成步骤是在手机上完成的,服务端只需要使用bsdiff去生成patch差分文件即可。
所以可以使用命令:make bsdiff仅生成bsdiff可执行文件。
生成差分文件
使用刚才编译出的bsdiff去生成差分文件,后接三个参数,第一个是旧版本的文件,第二个是新版本的文件,第三个是生成的差分文件:
1 | |
执行上述命令后就会生成patch文件,这个patch文件应该是小于app-v2.apk的。当更新时,用户只需要下载patch文件即可。以上就是整个服务端需要做的事了,就是编译bsdiff,然后生成差分文件。
在Android中使用bspatch合成安装包
bspatch是用于合成安装包的可执行文件。前面使用bsdiff将旧版本和新版本比较产生patch文件,这里的bspatch就是将旧版本和patch合并成新版本文件,与bsdiff是一个对应的过程,也是Android上主要使用的方法。
1 | |
引入源文件
在Android中使用也是比较简单的,首先新建一个native项目或者nativelib。然后在src/main/cpp目录下,创建一个目录bzip2-1.0.6。将对应的bzip2源文件放在这里。
注意,并不需要放入bzip2解压后的所有文件,而是生成libbz2.a相关的源文件即可。可以在bzip2-1.0.6解压后的目录中查看Makefile文件:
1 | |
上面是从Makefile中截取的一部分,从中可以看出我们需要blocksort.c、huffman.c、crctable.c、randtable.c、compress.c、decompress.c、bzlib.c七个文件,同时还需要两个头文件bzlib.h和bzlib_private.h。也就是一共9个文件,放入上述新建的zip2-1.0.6目录中。然后将bsdiff解压后的bspatch.c放入src/main/cpp中。
现在的目录结构应该是这样的:
1 | |
其中nativelib.cpp是新建module的时候自动生成的,可以修改成其他文件名,比如这里我就修改成了bspatch_merge.cpp。
编写CMakeLists.txt
然后编写CMakeLists.txt规则,将bzip2的源文件以及bspatch的源文件都添加进去:
1 | |
在bspatch.c中,入口方法也就是main函数,因为在Linux下最终是将bspatch.c编译成可执行文件的。而在Android中,我们最终是将它编译成一个共享库so,因此最好将main函数重命名一下,避免以后添加其他库的时候又有main函数导致冲突。这里将其改为patch_main。
并且,还需要将bspatch.c中引用的头文件#include<bzlib.h>改为#include "bzip2-1.0.6/bzlib.h"
编写代码
然后将NativeLib类重命名,改为PatchUtils,并定义成一个单例类:
1 | |
此时bsPatch方法应该是报红色错误的,鼠标放在上面根据提示可以直接生成jni方法,选择生成文件位置的时候记得选择bspatch.c中。或者不让他生成,直接在bspatch.c中手写即可,这样的话需要注意方法中的包名和类名要保持一致。
1 | |
首先通过extern关键字引入bspatch.c中的patch_main方法,然后调用。在可执行文件中,我们使用./bspatch old.apk new.apk patch命令去生成新文件,而对应的方法中,参数实际上是4个,因为第一个参数是函数本身,这里是需要注意的。
到这里就已经完成了Android中的引入了,使用的时候直接调用PatchUtils.bsPatch方法即可。当前安装的apk可以通过context.applicationInfo.sourceDir去获取。
详细代码上传至github仓库上了。
总结
使用bsdiff进行安装包的增量更新并不难,甚至可以说是非常简单,因为我们实际上在Android中仅仅是去调用bspatch中的main方法去合成而已。同样的,Linux编译bsdiff也很简单,只是稍微修改一下Makefile就行了。
使用bsdiff可以有效的降低更新时下载的安装包的体积,因为只需要下载对应的patch分包即可,而不需要下载完整的安装包文件,这也是我们最终的目的。
但是,实际使用中却很麻烦,因为每次更新后,都需要和之前的所有旧版本apk生成对应的patch分包,然后在获取更新信息的时候,根据传递的版本参数返回对应的patch下载地址。
这只是一个渠道包的情况,实际上我们线上每个应用商店上传的包都是不同的渠道包,而各个应用商店大概有十来个。也就是说,每次升级,至少要产生十几个patch分包,并且这还只是和一个旧版本apk产生的,而实际中,我们又非常多的旧版本,这也就意味着,patch分包的文件数量将会非常多…
当然,可以编写脚本文件来管理….
