Android 增量更新技术实现
为什么需要增量更新?
随着应用功能的丰富,APK 体积往往从几 MB 增长到几十甚至上百 MB。全量下载不仅消耗用户流量,在弱网环境下体验极差。增量更新通过仅传输新旧版本之间的差异数据,显著减少下载包大小,提升更新效率。
Android 增量更新技术通过比较新旧 APK 差异生成差分包,减少下载流量。核心使用 Bsdiff 算法生成补丁,Bspatch 在客户端合并。实现涉及 JNI 调用 C 库,处理编译兼容性,并在 Android 端完成下载、校验及静默安装流程。该方案适用于大体积应用的版本迭代优化。

随着应用功能的丰富,APK 体积往往从几 MB 增长到几十甚至上百 MB。全量下载不仅消耗用户流量,在弱网环境下体验极差。增量更新通过仅传输新旧版本之间的差异数据,显著减少下载包大小,提升更新效率。
增量更新的核心在于生成差分包(Patch)。其流程为:
本方案采用开源项目 bsdiff 进行文件内容比较,并使用 bzip2 进行压缩,以进一步减小差分体积。
将 bsdiff 的 .c、.cpp 源文件及 .h 头文件整理至独立文件夹,便于后续移植。
由于 Android 原生不支持直接调用 bsdiff,需通过 JNI 封装 C 函数供 Java 调用。
_CRT_SECURE_NO_WARNINGS 消除安全警告。_CRT_NONSTDC_NO_DEPRECATE。bsdiff.dll。public class BsDiff {
static {
System.loadLibrary("bsdiff");
}
/**
* 生成差分包
*
* @param oldFile 旧 APK 路径
* @param newFile 新 APK 路径
* @param patchFile 差分包输出路径
*/
public native void diff(String oldFile, String newFile, String patchFile);
}
JNIEXPORT void JNICALL Java_com_example_BsDiff_diff
(JNIEnv *env, jclass cls, jstring oldFile, jstring newFile, jstring patchFile) {
const char* oldPath = env->GetStringUTFChars(oldFile, NULL);
const char* newPath = env->GetStringUTFChars(newFile, NULL);
const char* patchPath = env->GetStringUTFChars(patchFile, NULL);
int argc = 4;
char* argv[4];
argv[0] = "bsdiff";
argv[1] = (char*)oldPath;
argv[2] = (char*)newPath;
argv[3] = (char*)patchPath;
bsdiff_main(argc, argv);
env->ReleaseStringUTFChars(oldFile, oldPath);
env->ReleaseStringUTFChars(newFile, newPath);
env->ReleaseStringUTFChars(patchFile, patchPath);
}
在 Android 端集成 bspatch 和 bzip2 库,实现差包解压合并。
在 app/src/main/cpp 下创建 CMakeLists.txt,引入 bzip2 和 bspatch 源码。
cmake_minimum_required(VERSION 3.4.1)
project(incrementupdate)
add_library(bspatch SHARED
bspatch.c
bzlib.c
crctable.c
compress.c
decompress.c
randtable.c
blocksort.c
huffman.c)
public class BspatchJNI {
static {
System.loadLibrary("bspatch");
}
/**
* 合并增量文件
*
* @param oldFilePath 当前 APK 路径
* @param newFilePath 合成后的新 APK 路径
* @param patchFilePath 增量文件路径
*/
public native void bspatchJNI(String oldFilePath, String newFilePath, String patchFilePath);
}
JNIEXPORT void JNICALL Java_com_example_BspatchJNI_bspatchJNI
(JNIEnv *env, jclass type, jstring oldPath, jstring newPath, jstring patchPath) {
const char* oPath = env->GetStringUTFChars(oldPath, 0);
const char* nPath = env->GetStringUTFChars(newPath, 0);
const char* pPath = env->GetStringUTFChars(patchPath, 0);
int argc = 4;
char* argv[4];
argv[0] = "bspatch";
argv[1] = (char*)oPath;
argv[2] = (char*)nPath;
argv[3] = (char*)pPath;
bspatch_main(argc, argv);
env->ReleaseStringUTFChars(oldPath, oPath);
env->ReleaseStringUTFChars(newPath, nPath);
env->ReleaseStringUTFChars(patchPath, pPath);
}
下载差分包后,建议计算 MD5 或 SHA256 值与服务端返回的签名比对,防止文件损坏或被篡改。
合并完成后,需调用系统安装器。推荐使用 PackageInstaller API 或 Intent 方式触发安装。
// 示例:通过 Intent 安装
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(newApkPath)), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
PackageManager 相关异常,确保回滚机制生效。Android 增量更新通过 bsdiff 算法有效降低流量消耗。实现关键在于 JNI 层的正确封装与编译环境的兼容性处理。在 Android 端需妥善集成 NDK 库,并完善下载、校验、合并及安装的全链路逻辑,确保用户体验流畅且稳定。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online