Android平台-实现应用增量更新
当我们要更新一个应用的时候,以前很多更新的做法是下载一个新版本去覆盖一个旧版本。随着现在应用越来越大,我们就不得不考虑流量的问题。
Google也意识到不断更新应用对用户流量的损耗,于是在Google I/O上提及了增量升级,或者叫差分升级的方法,并在新版本的Google Play中得到支持。
简单的来说,增量更新是指在进行更新操作时,只更新需要改变的地方,不需要更新或者已经更新过的地方则不会重复更新,增量更新与完全更新相对。
增量升级的原理其实不难,我们首先使用旧版本的apk与新版本的apk做差分,就能得到更新部分的补丁,也叫差分包。这个差分包虽然不是新旧版本的简单相减,但显而易见的是,用户不需要全部下载新版本apk了,我们只要下载更小的那个补丁包(差分包)。
在用户下载完差分包后,需要在手机端将它们组合起来。一般手机的应用安装在data/app下,我们可以复制出就版本apk至SD卡中,与下载好的差分包进行组合。最后得到一个新版本的apk后与服务器上的新版本进行校验,正确后就可以安装了。
NDK(native develop kits),是一个交叉编译的工具链。交叉编译是指:在一个平台下(CPU,操作系统)可以编译出在另外一个平台可以运行的代码,例如我们正要做的:windows AMD intel x86架构->手机android arm处理器。
下载地址:http://www.android-doc.com/tools/sdk/ndk/index.html
Cygwin是一个可以在windows下模拟出linux环境的一个工具,NDK必须在linux环境下进行编译等工作。
安装过程略。需要注意的是在Selectpackages安装界面中把Default改为Install,直接点击Default即可。
完成安装后,我们启动Cygwin,输入make -v命令,出现如下信息说明安装成功了。

我们cd到NDK的目录,执行./ndk-build命令,出现如下信息则说明大功告成。

2.3.在Eclipse中或MyEclipse中安装CDT插件(可选)
将下载好的安装包解压,把features、plugins目录下的文件对应的复制到Eclipse(MyEclipse)中即可,重启软件。
首先要生成旧版本和新版本的差分比patch文件,我们可以借助bsdiff开源库的windows版本。我们先下载bsdiff压缩包,解压后有以下两种工具。

Bsdiff.exe就是一个二进制差分工具,bspatch.exe就是相应的补丁合成工具了。我们的增量升级的差分包,是要在服务端完成的,也就是pc端去完成。我们打开命令行,转到bsdiff所在目录下,将新旧版本的apk也放到该目录下,执行命令:
bsdiff.exeoldName.apknewName.apkpatch.patch
Patch文件的名字可自己定义。如测试程序中的两个版本的apk。

将生成的差分包xx.patch放置到升级服务器上,供用户下载升级。如果多版本,则必须对不同版本都进行差分。如果版本跨度太大,可以选择整包升级。
测试中将上一步生成的Update.patch包放在发布服务器Tomcat上。
我们建一个Android工程,命名为UpdateDemo,用来实现应用增量更新功能。在工程中建一个包,编写本地方法。

我们首先用NDK编译出一个*.so文件。这个文件网上有编译好的,可以直接下载使用。附录中详述了如何去编译.so文件。
将下载好的.so文件放置于3.3.1新建的工程目录libs\armeabi文件夹下。

我们现在就可以调用patch方法来合成新apk了。在主程序MainAcitivity类中加入以下代码,该代码就是调用我们所需的so库文件。

这里得注意,我们的so文件前lib是系统生成加上去的,所以在调用中我们只需将名字去掉lib的名字作为参数。
无论在下载还是合成删除中,我们都必须获取手机SD卡的读写权限,我们在UpdateDemo工程中AndroidManifest.xml加入以下代码。

我们在UpdateDemo工程中加上添加一个更新按钮,实现点击按钮能够弹出下载选框,点击下载就可以下载服务器上的patch包了。我们设定下载的目录就是SD卡的跟目录。运行该程序,界面如图。

我们点击更新按钮,如下。

在合成过程中,我们需要旧安装包。在Android中非系统程序默认安装在data/app文件夹下,在非root情况下,我们可以对其本身安装包进行读操作,这样我们就可以将它复制到SD根目录下(和之前下载的patch文件同目录)。
在下载完成后,下载函数返回DOWN_OVER,线程接到这个信号后,调用PatchThread()函数。

我们在PatchThread()中调用复制旧安装包backupApplication(String packageName, String dest)方法。该方法第一个参数为安装包的ID号,如本例中的com.tutor.update,dest为复制目标路径。该方法中,主要函数如下。

该函数能够找到data/app下相应的安装包。
测试中我们下载完patch文件后,立刻复制旧安装包。

我们用360助手查看SD中文件。

接下来就可以调用本地方法合成新apk了。
同样在PatchThread()函数中,当复制成功后,我们就可以调用如下方法了。

参数都是旧、新和patch文件的路径。我们可以用360助手查看,新的apk已经合成,我们可以在程序中设定它的名字。这样,我们的新apk合成成功。

合成新apk后,我们无法预知它的正确性,在下载或在合成过程中,文件都有可能出错,所以紧接着我们需要对它进行MD5校验。
用MD5校验工具对服务器上的新apk进行校验,得到校验码。然后Android端合成新apk后对新apk也进行校验。我们在UpdateDemo工程中再建一个包,编写MD5类。

我们在MainAcitivity类中的PatchThread()函数中构造MD5对象,调用check方法即可。
用返回的校验值和服务器上的校验值对比,相同则进行安装,不同则提示校验失败。

校验正确后自动安装即可。紧接着校验函数,我们加入判断,如果校验正确,则调用以下方法;如果错误,我们就跳出提示信息。

安装完成后打开软件,已经是V2版本了,我们增量更新成功。

最后我们只要把用户下载的patch文件和旧apk删除即可,用户最终可以得到新apk,新apk用户可以自行处理。
删除时我们只要在安装后或在校验错误后调用delete()函数即可。

用助手查看SD目录,只有新apk了。

如果校验不通过,所有相关文件都会被删除。这样,我们就完成了我们的更新操作。
增量更新虽然不用将新apk的内容全部下载,但如果版本相差过大,或者apk本身非常小,还是建议下载整包。以下做了个小测试。

我们能够观察到,当apk不足1M时,差分包大小同样也有几百K,所以没必要去差分。当版本相差过大,增加内容非常多时,我们也建议直接整包更新。差分时包越大耗时越长。
测试中我们用到网上已有的so库,直接调用即可。我们也可以自己在客户端生成so文件。
我们先下载bzip2-1.0.6.tar.gz。新建一个Android工程,将可能用到的下列文件复制到新建的Android工程的jni目录下(没有的新建一个目录)。
下载地址:http://www.bzip.org/downloads.html。

在该jni目录下新建文件,命名Android.mk,配置如下。

打开cmd命令行,转到工程目录的bin文件夹下,输入以下命令。

系统会自动生成一个.h文件,我们把它拷贝到jni目录下。

我们在jni目录下新建com_example_jni_MainActivity.c,完成c代码。

打开Cygwin,转到工程目录,使用ndk命令编译成so库即可。

我们可以看到libBSdiff.so已经编译成功,在lib/armeabi目录下。

这样,我们就可以使用该so文件去合成新apk了。