增量更新有区别于全量更新,当服务器有新版本时候,客户端上传自己的版本号到服务器,服务器根据该版本与最新版本的 apk 生成差分包,客户端下载差分包以后与手机上已经安装的旧版本做合并生成新版apk。这种更新方式最大限度的节省了用户流量与下载等待时间。用到的开源库是大名鼎鼎的bsdiff和bzlib,他们的原理后面会分析。下面介绍整个增量更新的实现过程:
1. linux上gcc编译 bsdiff ,用以生成服务器端的差分工具
执行编译生成yzyBsdiff可执行文件(如果想生成so加--shared):
gcc -fPIC blocksort.c bsdiff.c bzip2.c bzip2recover.c compress.c crctable.c decompress.c huffman.c randtable.c spewG.c unzcrash.c bzlib.c -o yzyBsdiff
遇到的问题
- bsdiff.c:33:19: fatal error: bzlib.h: 没有那个文件或目录 #include <bzlib.h> 改为"bzlib.h"
- 不能有多个main方法,只能保留一个main(bsdiff.c里面的,其他的重命名),操作系统认为一个进程只能有为一个的main函数
- 不能写两个相同的文件编译,会报重复定义
2. linux上通过NDK交叉编译得到android arm 的 bspatch.so用以在Android端做增量更新合并
首先设置环境变量
export NDKROOT=~/dev/android-ndk-r9c
export SYSROOT=$NDKROOT/platforms/android-19/arch-arm
export GCC=$NDKROOT/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86/bin/arm-linux-androideabi-gc
编译生成可执行文件,保留main函数,为啥要加fPIC !必须加,否则会报 magic 7F45(如果anroid NDK调用so的话这一步可以掠过)
$GCC -fPIC -o yzybsPatchArm bspatch.c bzip2/blocksort.c bzip2/bzip2.c bzip2/bzip2recover.c bzip2/bzlib.c bzip2/compress.c bzip2/crctable.c bzip2/decompress.c bzip2/huffman.c bzip2/randtable.c bzip2/spewG.c bzip2/unzcrash.c --sysroot=$SYSROOT
编译生成so
第一步 生成.o文件
$GCC -fPIC -c bspatch.c bzip2/blocksort.c bzip2/bzip2.c bzip2/bzip2recover.c bzip2/bzlib.c bzip2/compress.c bzip2/crctable.c bzip2/decompress.c bzip2/huffman.c bzip2/randtable.c bzip2/spewG.c bzip2/unzcrash.c --sysroot=$SYSROOT
第二步 生成.so
$GCC -shared -Wl,-soname,libbspatchlib.so -o libbspatchlib.so blocksort.o bspatch.o bzip2.o bzip2recover.o bzlib.o compress.o crctable.o decompress.o huffman.o randtable.o spewG.o unzcrash.o --sysroot=$SYSROOT
可以使用file命令查看生成的so是否正确
yzy@yzy-P5QL-PRO:~/dev/workspace/bspatch/diffPatch$ file libbspatchlib.so
libbspatchlib.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, not stripped
3. Android端集成生成的libbspatchlib.so生成合并后apk
客户端程序通过https下载patch包与本地旧apk就行合并,生成新的apk,并安装达到增量更新的目的,下载patch包和通知更新的操作比较简单,博文并没给出。
关于如何把交叉编译生成的libbspatchlib.so集成到请参考我的另外一个博文:http://blog.csdn.net/nexttake/article/details/77202018
JNIEXPORT jint JNICALL Java_xxxx (JNIEnv *env, jclass jazz, jstring oldPath_jstr, jstring newPath_jstr, jstring patchPatch_jst) { int ret= -1; LOGD(" jni patch begin"); char *oldPath = (*env) -> GetStringUTFChars(env, oldPath_jstr, JNI_FALSE);//已经安装的旧版本的apk的路径 char *newPath = (*env) -> GetStringUTFChars(env, newPath_jstr, JNI_FALSE);//新生成的新版apk 的路径,合并完成以后启动系统安装器 char *patchPath = (*env) -> GetStringUTFChars(env, patchPatch_jst, JNI_FALSE);//从服务器下载的差分包patch的路径 int argc = 4; char *argv[4]; argv[0] = "TimBsPatch"; argv[1] = oldPath; argv[2] = newPath; argv[3] = patchPath; //如果成功ret等于0 ret = bspatch_main(argc,argv);//这个函数在bspatch.c中,执行完成以后新版本的apk就会保存到newPath
(*env) -> ReleaseStringUTFChars(env, oldPath_jstr, oldPath); (*env) -> ReleaseStringUTFChars(env, newPath_jstr, newPath); (*env) -> ReleaseStringUTFChars(env, patchPatch_jst, patchPath); return ret;
PS.
在android-studio中编译bspath源代码(将bspatch源码拷贝进as),其实在AS里面编译本质就是用NDK在交叉编译了.......,以后补充
最佳实践:
- 差分包的下载要使用https
- 当版本改动较大时候建议还是全量更新
- 某些手机可能会有无法读取已经安装的旧版本apk的流的问题