Android增量更新 - 客户端使用bsdiff差分包与原包合并

bsdiff简介

Android增量更新需要用到二进制差分工具:bsdiff

bsdiff and bspatch are tools for building and applying patches to binary files.

官方网站:http://www.daemonology.net/bsdiff/
bsdiff for windows:http://www.pokorra.de/coding/bsdiff.html

Android版本区别设定

(1)旧版本

app.gradle

1
2
versionCode 1
versionName "1.0"

activity_main.xml

1
2
3
4
5
6
7
8
9
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:text="1.0版本"
android:textSize="16sp"
android:textColor="#3BC1FF"
/>

(2)新版本

添加资源文件:assets/cctv.mp4

app.gradle

1
2
versionCode 2
versionName "2.0"

activity_main.xml

1
2
3
4
5
6
7
8
9
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:text="2.0版本"
android:textSize="16sp"
android:textColor="#3BC1FF"
/>

NDK实现pacth

Java层声明native方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BsPatch {

/**
* 合并
* @param oldFilePath
* @param newFilePath
* @param patchFilePath
*/
public static native void patch(String oldFilePath, String newFilePath, String patchFilePath);

static {
System.loadLibrary("BsPatch");
}
}

C层合并算法实现:diffupdate.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdint.h>
#include "cn_appblog_diffupdate_BsPatch.h"
#include "bspatch.h"

JNIEXPORT void JNICALL Java_cn_appblog_diffupdate_BsPatch_patch
(JNIEnv *env, jclass jclazz, jstring oldFilePath, jstring newFilePath, jstring patchFilePath) {
const char *oldFilePath_cstr = (*env)->GetStringUTFChars(env, oldFilePath, NULL);
const char *newFilePath_cstr = (*env)->GetStringUTFChars(env, newFilePath, NULL);
const char *patchFilePath_cstr = (*env)->GetStringUTFChars(env, patchFilePath, NULL);

char* argv[4];
argv[0] = "bspatch";
argv[1] = oldFilePath_cstr;
argv[2] = newFilePath_cstr;
argv[3] = patchFilePath_cstr;

bspatch_main(4, argv);

(*env)->ReleaseStringUTFChars(env, oldFilePath, oldFilePath_cstr);
(*env)->ReleaseStringUTFChars(env, newFilePath, newFilePath_cstr);
(*env)->ReleaseStringUTFChars(env, patchFilePath, patchFilePath_cstr);
}

Java层调用bspatch实现bsdiff差分包与原包合并

1
2
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
class AppUpdateTask extends AsyncTask<Void, Void, Boolean> {
@Override
protected Boolean doInBackground(Void... params) {
//1. 下载差分包
Log.i(TAG, "开始下载");
//File patchFile = DownloadUtil.download(PATCH_URL);
//String patchFilePath = patchFile.getAbsolutePath();
Log.i(TAG, "下载完成");
//2. 获取旧版本已经安装的apk文件(/data/app/)
String oldApk = ApkUtil.getSourceApkPath(MainActivity.this, getPackageName());
Log.i(TAG, "Old Apk: " + oldApk);
//3. 合并:oldFile+patchFile->newFile
BsPatch.patch(oldApk, NEW_APK_PATH, PATCH_FILE_PATH);
Log.i(TAG, "合并完成");
try {
Log.i(TAG, "New Apk MD5: " + MD5Util.getFileMD5String(NEW_APK_PATH));
} catch (IOException e) {
e.printStackTrace();
}
return true;
}

@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (!result) {
Toast.makeText(MainActivity.this, "差分包合并失败", Toast.LENGTH_SHORT).show();
}
//1. 获取两个版本的签名进行对比
//获取已经安装应用的签名
String oldSign = SignUtil.getInstalledApkSignature(MainActivity.this, getPackageName());
//获取未安装Apk文件的签名
String newSign = SignUtil.getUnInstalledApkSignature(NEW_APK_PATH);
if (TextUtils.equals(newSign, oldSign)) {
//2. 安装
ApkUtil.installApk(MainActivity.this, NEW_APK_PATH);
} else {
Toast.makeText(MainActivity.this, "签名不匹配", Toast.LENGTH_SHORT).show();
}
Toast.makeText(MainActivity.this, "差分包合并成功", Toast.LENGTH_SHORT).show();
}
}

app.gradle 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions"
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a'
}
}
}

externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}

安装测试

1
2
3
4
5
6
7
8
9
> adb push diff.patch /sdcard/  //将差分包导入到SD卡中,模拟在线更新
[100%] /sdcard/diff.patch

> adb install DiffUpdate_v1.apk //安装旧版Apk以待测试增量更新
[100%] /data/local/tmp/DiffUpdate_v1.apk
pkg: /data/local/tmp/DiffUpdate_v1.apk
Success

>

运行日志输出,可以看到合并生成的 New Apk MD5 与服务器端合并生成的完全一致

1
2
3
cc.androidios.diffupdate I/yezhou: Old Apk: /data/app/cc.androidios.diffupdate-1/base.apk
cc.androidios.diffupdate I/yezhou: 合并完成
cc.androidios.diffupdate I/yezhou: New Apk MD5: 735a02764167048e738a7f6ea99f2b86

核心技术点

bspatch功能调用

bapatch的使用方法为

1
bspatch oldfile newfile patchfile

bspatch.c的main函数为其入口函数,这里将函数名改为bspatch_main,变为普通函数,即可实现被其它函数调用。

1
int bspatch_main(int argc, char * argv[])

CMakeLists.txt 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
add_library( # Sets the name of the library.
BsPatch

# 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/diffupdate.c
src/main/cpp/bspatch.c
src/main/cpp/bzip2/bzlib.c
src/main/cpp/bzip2/crctable.c
src/main/cpp/bzip2/compress.c
src/main/cpp/bzip2/decompress.c
src/main/cpp/bzip2/randtable.c
src/main/cpp/bzip2/blocksort.c
src/main/cpp/bzip2/huffman.c
)

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :