一、定义
增量更新是指在进行更新操作时,只更新需要改变的地方,不需要更新或者已经更新过的地方则不会重复更新,增量更新与完全更新相对。对于我们app来说,一般对于用户来说用户的流量是很宝贵的,如果我们只改变了app中的一个变量,比如i。那么我们就需要重新发版,供用户下载更新整个app,而增量更新就不需要这样的操作了,虽然最终也是需要用户来安装的,但是用户下载的只是我们更改的部分。并且这个技术已经很完善。
二、使用
打成差分包过程windows和linux平台是有差异的
1、windows环境
下载bsdiff windows工具,可以自行百度下载。下载完成以后,进入目录,可以看到有两个工具bsdiff.exe和bspatch.exe.其中bsdiff是做差分包使用的,bspatch是用作合成新的app。
2、linux环境
3、配置android环境
(1)首先需要下载bispatch.c文件。下载地址bsdiff
(2)得到bispatch.c文件拷贝到项目的cpp目录下,发现需要bzlib.h,由于它不是系统文件,那么需要从外部引入
(3)下载bzip,它是可以实现压缩解压的过程,在合成差分包的过程用它进行压缩,那么在服务器下载的patch包就需要通过它进行解压。从它里面能够拿到bzlib.h相关代码文件,下载地址,bzip
(4)根据bzip提供的makeFile配置只保留以下文件,放入到cpp/bzip目录下
(5)此时编译发现还是提示找不到bzlib.h。因为我们上引入的方式是不正确,<>是只能引入系统文件,如果要引入外部文件,可以使用以下方式。重新编译成功了
#include "bzip/bzlib.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <unistd.h>
#include <fcntl.h>
(6)定义native层修复文件bspatcher.cpp,代码如下:
#include <jni.h>
// extern 声明在 bspatch.c
extern "C" {
extern int p_main(int argc, const char *argv[]);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_cxz_bspatchlib_BsPatcher_bsPatch(JNIEnv *env, jobject instance, jstring oldApk_,
jstring patch_, jstring output_) {
// 将Java字符串转为C/C++的字符串,转换为UTF-8格式的char指针
const char *oldApk = env->GetStringUTFChars(oldApk_, 0);
const char *patch = env->GetStringUTFChars(patch_, 0);
const char *output = env->GetStringUTFChars(output_, 0);
// bspatch, oldfile, newfile, patchfile
const char *argv[] = {"", oldApk, output, patch};
p_main(4, argv);
// 释放指向Unicode格式的char指针
env->ReleaseStringUTFChars(oldApk_, oldApk);
env->ReleaseStringUTFChars(patch_, patch);
env->ReleaseStringUTFChars(output_, output);
}
其中会调用bspatch.c中的p_main方法。传递两个参数,第一个参数固定为4,第二个参数传递的是指针数组,分别为olkApk(旧的apk),patch(差分包)和output(合成新的apk)
(7)将上述文件加入到CMakeLists.txt编译
cmake_minimum_required(VERSION 3.4.1)
# 查找文件系统中制定模式的路径,如:/*是匹配根目录下的所有文件
file(GLOB bzip_source ${CMAKE_SOURCE_DIR}/src/main/cpp/bzip/*.c)
# 设置本地动态库,编译生成动态库
add_library(
bspatcher # 模块名
SHARED # 动态库、分享库
src/main/cpp/native-lib.cpp
src/main/cpp/bspatch.c
${bzip_source}) # 源文件
# 查找系统库,日志输出库Log
find_library(
log-lib
log)
# 需要链接或者编译的库
target_link_libraries(
bspatcher
${log-lib})
(8)java层加载jni方法,定义一个工具类,代码如下
public class BsPatcher {
// 用于在应用程序启动时,加载本地的lib库
static {
System.loadLibrary("bspatcher");
}
/**
* 合成安装包
*
* @param oldApk 旧版本安装包,如1.1.1版本
* @param patch 差分包,Patch文件
* @param output 合成后新版本apk的输出路径
*/
public static native void bsPatch(String oldApk, String patch, String output);
}
(9)模拟activity调用update()方法如下:
public void update() {
// 从服务器下载 patch 到用户手机, SDCard 里面
new AsyncTask<Void, Void, File>() {
@Override
protected File doInBackground(Void... voids) {
// 获取旧版本路径(正在运行的apk路径)
String oldApk = getApplicationInfo().sourceDir;
//差分包路径,服务器下载到本地路径
String patch = new File(Environment.getExternalStorageDirectory(), "patch").getAbsolutePath();
//合成的新的apk保存路径
String output = createNewApk().getAbsolutePath();
if (!new File(patch).exists()) {
return null;
}
//开始合成,是一个耗时任务
BsPatcher.bsPatch(oldApk, patch, output);
//合成成功,重新安装apk
return new File(output);
}
@Override
protected void onPostExecute(File file) {
super.onPostExecute(file);
// 已经合成了,调用该方法,安装新版本apk
if (file != null) {
if (!file.exists()) return;
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//7.0及以上安装apk有差异,需要兼容
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Uri fileUri = FileProvider.getUriForFile(MainActivity.this, MainActivity.this.getApplicationInfo().packageName + ".fileprovider", file);
intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
//重启apk
MainActivity.this.startActivity(intent);
} else {
Toast.makeText(MainActivity.this, "差分包不存在!", Toast.LENGTH_LONG).show();
}
}
}.execute();
}
/**
* 创建合成后的新版本apk文件
*
* @return
*/
private File createNewApk() {
File newApk = new File(Environment.getExternalStorageDirectory(), "bsdiff.apk");
if (!newApk.exists()) {
try {
newApk.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
return newApk;
}
注意:比如之前已经发不过1.0、2.0、3.0等版本。那么就需要根据用户的版本来合成不同的差分包。如果是1.0版本就需要针对1.0版本生成差分包供用户下载,依次类推。所以实际项目中,就需要在后台配置shell脚本,根据接口传递的版本号,生成差分包