如何在不重新打包的前提下给Bundle追加补丁

解读

面试官问的是“不重新出整包”场景下的增量更新方案,核心诉求是:

  1. 玩家已安装的 APK/IPA 不动;
  2. 已下载到本地的 AssetBundle(下称 AB)可以原地追加或替换
  3. 补丁包体积最小、下载最快、回滚安全;
  4. 支持整包与补丁的版本混合校验,防止灰度错乱。

国内主流项目(腾讯、网易、米哈游、莉莉丝等)均把此能力作为上线标配,因此回答必须体现“可灰度、可回滚、可审计”的工程闭环,而不是单纯调用 API。

知识点

  1. AssetBundle 增量补丁算法

    • 基于文件级:只下发新增或改动的 AB,客户端用文件名+MD5比对;
    • 基于二进制差分:使用 bsdiff/xdelta3 生成 patch,旧 AB→新 AB 差分体积可压缩 70~90%;
    • 基于分块重组:把 AB 拆成 64 KB/128 KB 块,以哈希块表索引,只下载变化块(类似迅雷 P2SP)。
  2. Bundle 命名与版本隔离

    • HashName+版本号规则:ui_icon_7d35a1f0.bundle,当资源变动时文件名即变,天然避免缓存冲突;
    • CRC+文件大小双校验:防止 CDN 边缘节点缓存脏数据;
    • 使用**Scriptable Build Pipeline(SBP)**的 WriteResult 接口,可一次性输出“文件→Hash→依赖”映射表,作为补丁比对基准。
  3. 补丁包组织与下载

    • 生成PatchManifest(自定义 JSON/Binary),记录:
      • 新增列表:add_list<url, hash, size, patch_type>
      • 删除列表:remove_list<old_file_name>
      • 差分列表:diff_list<old_hash, patch_url, patch_size, new_hash>
    • 下载层使用分通道并发+断点续传(UnityWebRequest + Range),国内接入阿里云 OSS 分片+CDN 预加热
    • 补丁包用7z-LZMA二次压缩,移动端 CPU 解压 <300 ms,可再省 20% 流量。
  4. 本地应用与回滚

    • 下载完成后先写入补丁沙盒(Application.persistentDataPath/patch/),不覆盖原 Bundle;
    • 启动时自定义 AssetBundleProvider(Addressables 1.20+ 支持继承 ResourceLocationMap),优先加载沙盒路径,实现“覆盖式挂载”;
    • 若本次补丁导致崩溃,下次启动对比云端补丁黑名单(版本号+设备指纹),自动回滚并删除沙盒目录。
  5. 热更代码与 Bundle 耦合

    • 若补丁需要热更脚本逻辑,必须配合 Lua/ILRuntime 等热更 DLL,不可直接更新 Assembly-CSharp.dll(iOS 禁止 JIT);
    • 通过ScriptableObject 配置补丁映射表,让 Lua 层可以驱动新 Bundle 的加载与卸载,保持“资源+脚本”同版本。

答案

“我们项目采用基于二进制差分 + 沙盒覆盖的增量补丁方案,流程如下:

  1. 每次出包后,在 Jenkins 使用Scriptable Build Pipeline重新构建所有 AB,并生成文件哈希映射表(FileHashMap.json);
  2. 对比上次正式映射表,用bsdiff生成旧 AB→新 AB 的差分 patch,体积平均压缩到 15%;
  3. 把新增、删除、差分信息写入PatchManifest,上传 CDN;
  4. 客户端启动时拉取 PatchManifest,先比对本地persistentDataPath已有的 AB 哈希,缺失或变化文件走断点下载
  5. 下载完成先写入patch沙盒目录,不覆盖原包;
  6. 通过自定义AssetBundleProvider让 Addressables 优先加载沙盒路径,实现零重启生效;
  7. 若出现崩溃,服务端可下发回滚指令,客户端下次启动删除沙盒即回退到上一版本。 整个过程不重新打 APK/IPA,玩家只需下载差分包,iOS 审核无感,Android 渠道包无需提审。”

拓展思考

  1. 差分算法选型:bsdiff 在 500 KB 以下文件优势明显;>50 MB 的 Bundle 建议换zstd 流式差分分块哈希,可减少 30% CPU 耗时。
  2. CDN 缓存穿透:国内运营商缓存极凶,需在 URL 后加**?v=时间戳或使用阿里云私有回源签名**,否则玩家可能拉到旧 patch。
  3. 补丁灰度:可按用户 ID 尾号、渠道、设备分桶,在 PatchManifest 中增加gray_config字段,实现实时灰度开关。
  4. 可审计:每次补丁成功后上传本地日志+MD5 列表到大数据平台,方便追踪“补丁成功却资源缺失”的极端 case。
  5. WebGL 平台:由于浏览器无法写入本地文件,需把补丁包直接挂载到 Addressables 远程 catalog,走 IndexedDB 缓存,逻辑与原生保持一致。