概述
现在的APP更新频率非常高,apk的大小也在不断的变大。如果每次新版本的更新,都让用户去下载一个完整的apk,这对于用户的耐心和流量都是巨大的消耗。特别是做应用市场的,如果没有增量更新,那么就要额外的付出高额的流量费用。
原理
将手机已安装的apk与服务端最新的apk进行二进制对比,得到差分包,用户更新程序时,只需要下载差分包,并在本地使用差分包与已安装的apk合并成新版的apk。

工具
- bsdiff、bspatch - 下载地址:http://www.daemonology.net/bsdiff/ - 编译源码: - Step1: make - 出错信息:  
 - Step2: 修改- Makefile文件,将install:下面的- if和- endif各添加一个缩进:
 | 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | CFLAGS		+=	-O3 -lbz2
 PREFIX		?=	/usr/local
 INSTALL_PROGRAM	?=	${INSTALL} -c -s -m 555
 INSTALL_MAN	?=	${INSTALL} -c -m 444
 
 all:		bsdiff bspatch
 bsdiff:		bsdiff.c
 bspatch:	bspatch.c
 
 install:
 ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
 .ifndef WITHOUT_MAN
 ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
 .endif
 
 |  
 
- Step3: make - 错误信息:  
 - Step4: 在bspath.c中添加- #include <sys/types.h>
 - Step5: make  
 
生成差分包
| 1
 | ./bsdiff v1.0.0.apk v1.0.1.apk 1.0.0-to-1.0.1.patch
 | 

合并差分包
| 1
 | ./bspatch v1.0.0.apk new.apk 1.0.0-to-1.0.1.patch
 | 

对比文件是否一样:

在Android项目中使用bspatch
Step1:下载依赖库bzip的源码(官网域名过期),可以从我的GitHub项目中拷贝:
下载地址:https://github.com/xch168/BsPatchTest
Step2:将bzip库,导入到Android项目中;

Step3:将bsdiff中的bspatch.c文件导入到Android项目中,并将bspatch.c中的#include <bzlib.h>改成#include "bzip2/bzlib.h"

Step4:编写CMakeLists.txt
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | cmake_minimum_required(VERSION 3.4.1)
 
 aux_source_directory(./src/main/cpp SRC)
 
 aux_source_directory(./src/main/cpp/bzip2 SRC_BZIP)
 
 
 list(APPEND SRC ${SRC_BZIP})
 
 add_library( bspatch
 SHARED
 ${SRC})
 
 find_library( log-lib
 log )
 
 target_link_libraries( bspatch
 ${log-lib} )
 
 | 
Step4:编写代码
Java层:
BsPatchUtil.java
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | public class BsPatchUtil {
 static {
 System.loadLibrary("bspatch");
 }
 
 public static native int patch(String oldApk, String newApk, String patch);
 
 }
 
 | 
调用:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 
 | public class MainActivity extends AppCompatActivity {
 private final File destApk = new File(Environment.getExternalStorageDirectory(), "dest.apk");
 private final File patch = new File(Environment.getExternalStorageDirectory(), "1.0.0-to-1.0.1.patch");
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 TextView versionText = findViewById(R.id.tv_version);
 versionText.setText("App版本:v" + getVersionName());
 }
 
 public void update(View view) {
 new Thread(new Runnable() {
 @Override
 public void run() {
 BsPatchUtil.patch(getApkPath(), destApk.getAbsolutePath(), patch.getAbsolutePath());
 
 if (destApk.exists()) {
 install(MainActivity.this, destApk);
 }
 }
 }).start();
 }
 
 private void install(Context context, File apk) {
 Intent intent = new Intent(Intent.ACTION_VIEW);
 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 intent.setDataAndType(Uri.fromFile(apk), "application/vnd.android.package-archive");
 context.startActivity(intent);
 }
 
 
 
 
 
 
 private String getApkPath() {
 ApplicationInfo applicationInfo = getApplicationInfo();
 return applicationInfo.sourceDir;
 }
 
 private String getVersionName() {
 String versionName = "";
 try {
 PackageInfo packageInfo = getApplicationContext()
 .getPackageManager()
 .getPackageInfo(getPackageName(), 0);
 versionName = packageInfo.versionName;
 } catch (PackageManager.NameNotFoundException e) {
 e.printStackTrace();
 }
 return versionName;
 }
 }
 
 | 
C层:
bspatch-util.h:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | #include <jni.h>#include <malloc.h>
 #include <string.h>
 #include <stdio.h>
 #include "bzip2/bzlib_private.h"
 
 int executePatch(int argc, char *argv[]);
 
 JNIEXPORT jint JNICALL
 Java_com_github_xch168_bspatchtest_BsPatchUtil_patch(JNIEnv *env, jclass type, jstring oldApk_, jstring newApk_, jstring patch_);
 
 | 
bspatch-util.c
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | #include "bspatch-util.h"
 JNIEXPORT jint JNICALL
 Java_com_github_xch168_bspatchtest_BsPatchUtil_patch(JNIEnv *env, jclass type, jstring oldApk_, jstring newApk_, jstring patch_) {
 
 int argc = 4;
 char *argv[argc];
 argv[0] = "bspatch";
 argv[1] = (char*)((*env)->GetStringUTFChars(env, oldApk_, 0));
 argv[2] = (char*)((*env)->GetStringUTFChars(env, newApk_, 0));
 argv[3] = (char*)((*env)->GetStringUTFChars(env, patch_, 0));
 
 int ret = executePatch(argc, argv);
 
 (*env)->ReleaseStringUTFChars(env, oldApk_, argv[1]);
 (*env)->ReleaseStringUTFChars(env, newApk_, argv[2]);
 (*env)->ReleaseStringUTFChars(env, patch_, argv[3]);
 
 return ret;
 }
 
 | 
Step5:将通过bsdiff生成的差量包1.0.0-to-1.0.1.patch push到手机sdcard目录
Step6:运行程序,点击”增量更新“

参考链接
- Android 增量更新 (一) NDK 编译 Bsdiff 
- Android 增量更新完全解析 
- Android应用增量更新 - Smart App Updates 
- 漫谈Android 增量更新 
- 编译和使用bsdiff