91、增量更新与更新策略:BSDiff、增量包生成、强制更新与静默更新
应用更新这件事,说白了就是「怎么让用户用上新版本」。全量更新最简单——直接下载整个APK。但问题也明显:包太大,用户流量扛不住,更新率自然就低。
我做过一个项目,APK从30MB涨到80MB,结果一周内更新率掉了15%。用户不是不想更新,是嫌流量贵。后来我们切了增量更新,更新率才慢慢回升。
这一章,我就把增量更新的核心讲清楚。包括BSDiff的原理、增量包怎么生成、强制更新和静默更新的落地策略。嗯,都是实战中踩过的坑。
什么是增量更新?
增量更新的思路很简单:只下载「变化的部分」。比如你从v1.0升级到v1.1,服务器只下发两个版本之间的差异数据,客户端拿到后和本地APK合并,生成新APK。
这样做的好处很明显:
- 节省流量:增量包通常只有全量包的10%~30%
- 提升更新率:用户更愿意点「更新」
- 减少服务器压力:传输数据量小了,带宽成本也降了
但增量更新也有代价:客户端需要做合并计算,对CPU和内存有一定消耗。不过现在手机性能普遍过剩,这点开销可以忽略。
BSDiff:增量更新的核心算法
市面上主流的增量算法有bsdiff、hdiffpatch、xdelta等。我个人最常用的是BSDiff。它由Colin Percival提出,专门针对二进制文件做差异计算。
BSDiff的原理可以概括为三步:
- 扫描旧文件:将旧APK拆成固定大小的块(通常是16KB)
- 匹配新文件:在新APK中找相同或相似的块
- 生成差异数据:记录「哪些块没变」「哪些块变了」「变的内容是什么」
合并时,客户端把旧APK的块和差异数据拼起来,就得到了新APK。
核心公式:新APK = 旧APK + 差异数据
差异数据 ≈ 新APK中「与旧APK不同」的部分
增量包生成:服务端怎么做?
生成增量包需要在服务端完成。我一般用Google的bspatch工具,配合脚本自动化处理。
下面是一个典型的生成流程:
# 假设旧APK为 app-v1.0.apk,新APK为 app-v1.1.apk
# 生成增量包 app-v1.0-to-v1.1.patch
bsdiff app-v1.0.apk app-v1.1.apk app-v1.0-to-v1.1.patch
生成的.patch文件就是增量包。大小通常在几百KB到几MB之间,取决于两个版本的差异程度。
我在项目中遇到过一个问题:如果两个版本之间改动太大(比如重构了资源目录),增量包可能比全量包还大。这时候我会加一个判断——如果增量包超过全量包的80%,就直接下发全量包。
经验之谈:建议在服务端维护一个「版本差异表」,记录每个旧版本到新版本的增量包大小。当增量包超过阈值时,自动回退到全量更新。
客户端合并:如何应用增量包?
客户端拿到增量包后,需要调用bspatch的合并函数。Android上我推荐使用libbspatch.so,通过JNI调用。
核心代码如下:
// 合并增量包
public static boolean applyPatch(String oldApkPath, String newApkPath, String patchPath) {
try {
// 调用native方法
int result = BSPatch.patch(oldApkPath, newApkPath, patchPath);
return result == 0;
} catch (Exception e) {
Log.e("IncrementalUpdate", "合并失败", e);
return false;
}
}
合并完成后,会生成一个新的APK文件。接下来就是安装流程了。
注意:合并过程需要保证旧APK文件完整且未被篡改。如果用户通过非官方渠道安装了修改版APK,合并会失败。我建议在合并前做一次签名校验。
强制更新 vs 静默更新
更新策略决定了「用户怎么知道有新版本」「用户能不能拒绝更新」。我把它分为两种:
| 策略 | 适用场景 | 用户体验 | 实现方式 |
|---|---|---|---|
| 强制更新 | 安全漏洞修复、重大功能变更 | 用户必须更新,否则无法使用 | 弹窗拦截,无关闭按钮 |
| 静默更新 | 小版本迭代、性能优化 | 用户无感知,后台自动完成 | 后台下载+合并+安装 |
强制更新的实现
强制更新说白了就是「你不更新就别想用」。我一般这样设计:
- 启动时检查版本号,如果低于最低支持版本,弹出全屏更新页面
- 页面没有关闭按钮,只有「立即更新」
- 更新完成后自动重启App
我曾经在一个金融类项目中做过强制更新。当时发现了一个严重的安全漏洞,必须让所有用户升级。我们直接在后端配置了min_version字段,低于该版本的客户端全部被拦截。
静默更新的实现
静默更新更考验技术。它需要满足几个条件:
- App在后台运行(或用户正在使用但无感知)
- 下载增量包
- 合并生成新APK
- 调用
PackageInstaller静默安装(需要系统权限或root)
对于普通App,静默安装很难实现——Android系统不允许普通App静默安装APK。我常用的折中方案是:
- 后台下载并合并增量包
- 合并完成后,弹出通知「新版本已准备好,点击重启」
- 用户点击通知后,跳转到安装页面
这样既减少了用户等待时间,又绕过了静默安装的限制。
我的建议:不要追求完全的静默安装。Android系统对安全越来越严格,强行静默安装反而容易出问题。让用户点一下「安装」,体验上已经足够好了。
更新策略的决策流程
在实际项目中,我会根据版本号、用户网络状态、设备电量等因素,动态决定更新策略。下面是我常用的一张决策流程图:
这张图的核心逻辑是:先判断是否强制更新,如果是就直接走强制流程。如果不是,再根据网络环境决定是静默下载还是提示用户。最后统一合并安装。
避坑指南
增量更新看着简单,实际落地时坑不少。我把自己踩过的坑列出来:
- 签名不一致导致合并失败:旧APK必须是官方签名版本。用户从第三方渠道下载的修改版,合并时大概率会失败。我建议在合并前做签名校验。
- 增量包过大:如果两个版本之间资源文件大量变更,增量包可能比全量包还大。建议设置阈值,超过80%就回退到全量更新。
- 合并耗时过长:低端手机上合并一个80MB的APK可能需要5~10秒。建议在后台线程执行,不要阻塞UI。
- 静默安装权限问题:Android 10以上对静默安装限制更严。普通App几乎无法实现真正的静默安装。别在这上面浪费太多精力。
我的习惯:每次发布新版本前,我会在测试机上跑一遍「从v1.0到v1.1」「从v1.0到v1.2」等所有可能的升级路径。确保每个增量包都能正确合并。这个步骤虽然繁琐,但能避免线上事故。
总结
增量更新是提升用户体验的重要手段。BSDiff算法成熟稳定,服务端生成增量包、客户端合并安装,整个链路并不复杂。关键在于根据业务场景选择合适的更新策略——强制更新用于紧急修复,静默更新用于日常迭代。
嗯,这一章就到这里。如果你在落地过程中遇到问题,欢迎交流。
公众号:蓝海资料掘金营,微信deep3321