增量更新在Android开发中是一种很常见的技术。
增量更新的原理
增量更新的原理非常简单,就是将本地apk与服务器端最新版本比对,并得到差异包。用户更新App时只需要下载差异包即可。
关键有两点:
- 根据新旧版本的APK生成差异包
- 使用差异包和旧版的APK合并成新版APK
生成差异包使用的是一个C++的开源库:bsdiff
百度第一项就是
由于我们是在windows环境下开发,所以下载windows的版本
实验
直接使用不确定因素太多,所以先实验后再使用,这里先使用Eclipse和Visual Studio来实验bsdiff是否可以完成拆分和合并的工作,并熟悉流程。
准备工作
生成新旧两个版本的APK。
旧版的APK需要完成差异包和旧版APK合并的工作,所以需要用到NDK开发,这一块放在后面详细介绍。
这里使用最普通的APK,先介绍如何生成差异包。
生成差异包
新建Eclipse工程 BsDiffTest
该工程用来调用C/C++的代码。核心代码是C/C++。此处主要是Jni的调用。新建Visual Studio的C++工程 BsDiffTest。
该工程是用来生成dll动态库(windows环境)或者so动态库(linux环境)。-
复制bsdiff的源码到VS工程的工程目录下。并删除bspatch.cpp
该文件是用于合并的,在生成差异包的时候使用的是bsdiff.cpp
复制 jni.h和jni_md.h到VS工程的工程目录下。
-
Eclipse工程中新建类
package com.relengxing; public class BsDiff { static{ System.loadLibrary("BsDiffTest"); } /** * 调用本地方法生成差异包 * @param oldfile * @param newfile * @param patchfile */ public native static void diff(String oldfile,String newfile,String patchfile); }
在main方法中调用
package com.relengxing;
public class BsDiffTest {
public static final String OLD_APK_PATH = "D:/apk/app_old.apk";
public static final String NEW_APK_PATH = "D:/apk/app_new.apk";
public static final String PATCH_PATH = "D:/apk/apk.patch";
public static void main(String[] args) {
BsDiff.diff(OLD_APK_PATH, NEW_APK_PATH, PATCH_PATH);
}
}
-
使用javah生成BsDiff类的头文件。如果此处不会请参考Jni系列,里面有详细说明。
把生成的头文件复制到VS的工程目录
修改#include<jni.h>为#include“jni.h”
把头文件中定义的方法赋值到bsdiff.cpp的最后并实现。JNIEXPORT void JNICALL Java_com_relengxing_BsDiff_diff (JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr) { int argc = 4; char *argv[4]; char *oldfile = (char *)env->GetStringUTFChars(oldfile_jstr, NULL); char *newfile = (char *)env->GetStringUTFChars(newfile_jstr, NULL); char *patchfile = (char *)env->GetStringUTFChars(patchfile_jstr, NULL); argv[0] = "bsdiff"; argv[1] = oldfile; argv[2] = newfile; argv[3] = patchfile; bsdiff_main(argc, argv); env->ReleaseStringUTFChars(oldfile_jstr, oldfile); env->ReleaseStringUTFChars(newfile_jstr, newfile); env->ReleaseStringUTFChars(patchfile_jstr, patchfile); }
-
修改bsdiff.cpp中的main方法为bsdiff_main
在Java_com_relengxing_BsDiff_diff中调用bsdiff_main()方法,
该方法需要传递两个参数int bsdiff_main(int argc,char *argv[])
int argc,char *argv[];
在源码中可以找到以下代码,意思就是argc必须等于4,否则就报错
if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);在源码中依次可以找到如下注释
而在argv[]中第一个参数是无用的,随便填什么都可以,第二个参数是旧版Apk的地址,第三个参数是新版Apk的地址,第四个参数是生成的差分包的地址。
/* Allocate oldsize+1 bytes instead of oldsize bytes to ensure
that we never try to malloc(0) and get a NULL pointer */
//org:
//if(((fd=open(argv[1],O_RDONLY,0))<0) ||
// ((oldsize=lseek(fd,0,SEEK_END))==-1) ||
// ((old=malloc(oldsize+1))==NULL) ||
// (lseek(fd,0,SEEK_SET)!=0) ||
// (read(fd,old,oldsize)!=oldsize) ||
// (close(fd)==-1)) err(1,"%s",argv[1]);
//new:
//Read in chunks, don't rely on read always returns full data!
/* Allocate newsize+1 bytes instead of newsize bytes to ensure
that we never try to malloc(0) and get a NULL pointer */
//org:
//if(((fd=open(argv[2],O_RDONLY,0))<0) ||
// ((newsize=lseek(fd,0,SEEK_END))==-1) ||
// ((_new=malloc(newsize+1))==NULL) ||
// (lseek(fd,0,SEEK_SET)!=0) ||
// (read(fd,_new,newsize)!=newsize) ||
// (close(fd)==-1)) err(1,"%s",argv[2]);
//new:
//Read in chunks, don't rely on read always returns full data!
/* Create the patch file */
//org:
//if ((pf = fopen(argv[3], "w")) == NULL)
//new:
//if((fd=open(argv[3],O_CREAT|O_TRUNC|O_WRONLY|O_BINARY|O_NOINHERIT,0666))<0)
-
工程中有很多错误,我们依次来解决。
根据自己的环境选择生成dll动态库
编译工程
当遇到以上错误时,在解决方案的属性其他选项中添加
-D _CRT_SECURE_NO_WARNINGS
然后再编译:
当遇到以上错误时,在解决方案的属性其他选项中添加
-D _CRT_NONSTDC_NO_DEPRECATE
再编译:
遇到以上错误时把SDL检查选为否
再编译:成功生成dll动态库
-
自行修改环境变量,在jni系列有详述。
然后在eclipse中运行。运行需要一段时间,运行完成后在我们设置的文件夹下可以查找到一下文件图中apk.patch就是差异包
合并差分包(windows端)
合并差分包是在手机端做的事,这里先在windows端实验一遍,合并和生成差分包有相似之处,这里就简单的写了。
- 同样新建Eclipse工程BsPatchTest。
- 新建VS工程BsPatchTest。
- 复制bsdiff的源码到VS工程的工程目录下。并删除bsdiff.cpp该文件是用于合并的,在生成差异包的时候使用的是bspatch.cpp
- 复制 jni.h和jni_md.h到VS工程的工程目录下。
- Eclipse工程中新建类
- 使用javah生成BsDiff类的头文件
-
修改bspatch.cpp中的main方法为bspatch_main
在Java_com_relengxing_BsDiff_diff中调用bsdiff_main()方法, - 工程中有很多错误,我们依次来解决。
- 自行修改环境变量,在jni系列有详述。
头文件的代码复制到bspatch.cpp中实现:
JNIEXPORT void JNICALL Java_com_relengxing_BsPatch_patch
(JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr) {
int argc = 4;
char* oldfile = (char*)env->GetStringUTFChars(oldfile_jstr, NULL);
char* newfile = (char*)env->GetStringUTFChars(newfile_jstr, NULL);
char* patchfile = (char*)env->GetStringUTFChars(patchfile_jstr, NULL);
//参数(第一个参数无效)
char *argv[4];
argv[0] = "bspatch";
argv[1] = oldfile;
argv[2] = newfile;
argv[3] = patchfile;
bspatch_main(argc, argv);
env->ReleaseStringUTFChars(oldfile_jstr, oldfile);
env->ReleaseStringUTFChars(newfile_jstr, newfile);
env->ReleaseStringUTFChars(patchfile_jstr, patchfile);
}
把之前的文件名修改一下
运行eclipse工程
经过比较,生成的文件和之前的文件大小一样。
所以使用bsdiff来做增量更新是可以实现的。
实际使用
服务器端
服务器端有各种语言,使用bsdiff的话,核心代码还是C/C++,最后就是和java一样调用C/C++。由于我不懂服务器端的技术。。所以这里使用一个最偷懒的办法,手动生成差分包,然后把差分包放在tomcat里,这样就可以在局域网内访问了。
客户端
Android客户端需要实现几个功能
- 下载差分包
- 合并差分包
- 安装合并后的APK
这里我是使用Android Studio 2.2 Bate2开发的
新建工程
如图勾选此处
关于CMakeLists.txt这个文件我也不是很懂,反正前面是#的就是注释掉的。
这一行和版本号有关系
cmake_minimum_required(VERSION 3.4.1)
这一段大概意思就是创建并命名一个库,设置它是STATIC或者SHARED,并提供了其源代码的相对路径,你可以定义多个库,CMake会为你构建,Gradle自动打包共享库给你的apk。
这里可以把下面这段的native-lib和src/main/cpp/native-lib.cpp改成自己的
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
# Associated headers in the same location as their source
# file are automatically included.
src/main/cpp/native-lib.cpp )
后面还有两段这里先不研究了,以后如果有研究再写下来。
代码搬运工
Android是Linux的。
所以bsdiff要下载linux版本的
这里只有两个文件是有用的,就是两个点C文件。
bsdiff用来生成差分包,bspatch用来合并差分包。
但是只有这个.c文件是不够的,这个项目还依赖bzip2,所以再去把bzip2下载下来,这里把bspatch.c复制到工程的Cpp目录下。删除之前的native-lib.cpp。
把下载下来的bzip2源码解压出来,把所有的.c和.h文件复制粘贴到cpp目录下新建一个bzip2文件夹中。
修改CMakeLists.txt中下面两段,同步一下。
修改要加载的动态库
在bspatch.c中添加如下文件,至于为什么是这些文件,听说是多编译几次就知道了。
#include "bzip2/bzlib.c"
#include "bzip2/crctable.c"
#include "bzip2/compress.c"
#include "bzip2/decompress.c"
#include "bzip2/randtable.c"
#include "bzip2/blocksort.c"
#include "bzip2/huffman.c"
动态库源码的搬运工作到这里就结束了,剩下的就得自己写代码了。
写调用jni的类
新建一个类BsPatch
patch的方法名是红色的,是因为定义了该方法,但是在本地的方法中却找不到,这时按Alt+Enter快捷键
这里重新生成了一个jni的文件夹,在jni的文件夹下又生成了一个bspatch.c的文件。
最上面那行提示代码的意思是这个文件没有在这个工程中,加我们添加到工程里面去......不理他,只复制我们要的东西。把这个里面的东西复制到cpp\bapatch.c的最后。和之前一样修改main为bspatch_main,在本地方法中调用
至于怎么下载,怎么安装这块就不详细说了,具体的到时候看Git的代码好了。核心的代码就是合并这块,合并就是调用刚刚写的这个函数,然后把三个地址传递进来。