Android增量更新研究
很多线上的android
apk都有增量更新功能,他能使你更新app的时候,无需完全下载一个新的安装包,而只需下载一个新旧版本之间的一个补丁(patch),然后在本地合成新的安装包。
环境:ubuntu16.04/android
studio2.1
工具:bsdiff/bspatch
说明:http://www.daemonology.net/bsdiff/
最新版本地址:http://www.daemonology.net/bsdiff/bsdiff-4.3.tar.gz
下载后解压:
发现是C语言写的,需要编译,终端打开当前路径,输入:make,回车:
注意:这里的makefile有点儿问题,编译总是报错,需要做一些修改:
CC=gcc
LDFLAGS=
CFLAGS=-Wall
-O3 -g -lbz2
PREFIX
?= /usr/local
INSTALL_PROGRAM
?= ${INSTALL} -c -s -m 555
INSTALL_MAN
?= ${INSTALL} -c -m 444
all:
bsdiff bspatch
bsdiff:
bsdiff.c
$(CC)
bsdiff.c $(CFLAGS) $(LDFLAGS) -o bsdiff
bspatch:
bspatch.c
$(CC)
bspatch.c $(CFLAGS) $(LDFLAGS) -o bspatch
install:
${INSTALL_PROGRAM}
bsdiff bspatch ${PREFIX}/bin
.ifndef
WITHOUT_MAN
${INSTALL_MAN}
bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif
编译以后,生成了两个文件:bsdiff和bspatch。
bsdiff是用于生成patch的,命令格式:bsdiff
oldfile newfile patchfile
比如,旧版本:test1.0.apk,新版本:test2.0.apk,patch文件名称:test1.0_2.0.patch
输入:./bsdiff
test1.0.apk test2.0.apk test1.0_2.0.patch,等待几十秒,目录下会生成
test1.0_2.0.patch。
以上就是生成patch包的过程,这就完成了。
--------------------------------------华丽的分割线--------------------------------------
生成patch包以后,就是合成包了。
Bspatch就是用来合成包的,语法和bsdiff一模一样,参数顺序都一样:
bspatch
oldfile newfile
patchfile,只不过,bsdiff生成的文件是patchfile,而bspatch生成的文件是newfile。
试一下,为了区分,合成的新包,加了个”_NEW”后缀。如图:
命令测试成功了。
下一步,我们需要在android里实现这个功能。
因为需要用到jni,所以需要首先搭建NDK,不再介绍。
我们先计划一下:通过更新接口,查询时候有新版本,服务器,根据请求者的版本号,生成对应的patch包,命名规则:应用名_oldVersion-newVersion.patch,合成的新包命名:应用名_newVersion.apk,应用下载相应patch到cache目录,如果命名符合规则,则合成新包。合成新包以后,自动安装。
1,创建一个工程:PatchDemo:
2,创建一个新android
library
module,名字:bspatch;在bspatch模块的main目录下新建一个jni目录,将bsdiff目录里的bspatch.c复制到jni目录。因为项目依赖bzip2,需要自行下载bzip2,然后解压,带目录复制到jni目录,其实被依赖的只是部分文件,我并没有一一筛选。
再看下项目结构:
3,然后回到app
module,创建类BSPatcher,声明方法:
packagecom.zhouweixian.patchdemo.patch;
public
classBSPatcher {
static{
System.loadLibrary("bspatch");
}
public static native voidapplyPatch(String oldPath, StringnewPath, String patchPath);
}
4,打开as自带的终端,cd
app/src/main/java/,
使用javah生成头文件。javah
com.zhouweixian.patchdemo.patch.BSPatcher
可以看到,头文件已经生成了,剪切到bspatch
module的jni目录
打开bspatch.c,将main函数改一下名字,这里改成domain了。因为不再是主函数,而是要生成.so文件,被我们的java代码调用。
5,回到BSPatcher文件,找到里面的applyPatch方法,正常情况下,这个函数应该显示红色,光标选中,代码提示alt+enter(这个因人而异,快捷键可以调)如图:
选中第一条回车,as会自动创建一个格式良好的jni方法名,在bspatch.c文件里:
这样代码也就差不多了,打开app
module的build.gradle文件,添加一个NDK代码块:
ndk{
moduleName"bspatch"
ldLibs"log","z","m"
abiFilters"armeabi","armeabi-v7a","x86"
}
然后Build-->make
project,make完成,在bspatch
module里,找到build/intermediates/ndk/debug/lib目录,打开,我们发现,各个架构的.so文件都已生成:
将lib目录下的内容复制到bspatch/jniLibs目录下:
添加app对bspatch的依赖
到这里,jni的工作已经没有了,剩下的,就是如果调用bspatch的问题了。
创建一个PatchTask.java。因为生成新包的过程比较耗时,需要在工作线程中完成。
packagecom.zhouweixian.patchdemo.patch;
importandroid.os.AsyncTask;
importcom.zhouweixian.patchdemo.MyApplication;
/**
*将旧安装包和补丁合成新安装包,输入参数,补丁文件路径,合成的新包的路径
*输出参数,合成成功的新安装包的路径,如果失败返回null
* Created by zwx on 16-10-18
*/public
classPatchTaskextendsAsyncTask{
@Override
protectedString doInBackground(String... params){
try{
String oldVersionPath =AppUtils.getOldVersionPath(MyApplication.getInstance());
BSPatcher.applyPatch(oldVersionPath, params[0],
params[1]);
returnparams[0];
}catch(Exceptione) {
e.printStackTrace();
}
return null;
}
@Override
protected voidonPostExecute(Stringstr) {
super.onPostExecute(str);
if(str
!=null){
AppUtils.install(MyApplication.getInstance(), str);
}
}
@Override
protected voidonProgressUpdate(Integer... values) {
//可能没有明确的进度,需要提示用户等待,成功合成新安装包后自动安装super.onProgressUpdate(values);
}
}
在MainActivity里,添加一个button,在点击事件里,处理安装包的合成:
@Override
public
voidonClick(View v) {
switch(v.getId()) {
caseR.id.button:
newVersion=mEditText.getText().toString();
newPatchTask().execute(PathConstant.getNewApkPath(newVersion),
PathConstant.getPatchPath(newVersion));
break;
}
}
经测试,将新安装包放在应用的缓存目录会导致包解析失败,具体原因不明。