一次热更让玩家多下了几百个根本没变的 Bundle,我是怎么把它修好的

先说人话:我们遇到了什么鬼
事情是这样的。我们游戏用的是 Unity + Addressables,热更就是往 CDN 上丢新的 bundle 文件,玩家打开游戏时比对一下,只下载变了的那些。
按理说,你这次更新就改了一张图,那玩家应该只多下那一个 bundle 才对。结果我们打出来的热更包里,躺着几百个 bundle,仔细一看,内容一个字节都没变,就是文件名变了。
文件名一变,客户端就认为它是"新文件",于是玩家吭哧吭哧把这几百个 bundle 重新下载了一遍。那次 iOS 热更,光是"内容没变、纯改名"的 bundle 就占了 272 MB,整个 patch 的一大半体积是白下的。
你说气不气人。
问题到底出在哪
研究了一圈,根子在一个很隐蔽的地方:bundle 的文件名,是根据哈希算出来的。
大概长这样:
文件名 = Hash( 我自己的内容 + 我依赖的那些 bundle 的名字列表 )
注意最后那半句——"我依赖的那些 bundle 的名字列表",它还不是直接依赖,是一路递归展开出来的"传递闭包"。啥意思呢?就是"A 依赖 B,B 依赖 C",那 A 的哈希里不仅记了 B,还把 C 也记进去了。
问题就来了:
只要你新增了一个 bundle,而这个 bundle 又被某个"被大家广泛依赖"的大 hub(比如 OtherData、通用 UI、要塞这种)依赖了,那这个新名字就会一路钻进所有依赖这个 hub 的 bundle 的哈希里。结果就是——几百个 bundle 的哈希全变了,文件名全改了,但内容一个字节都没动。
说白了:你只是往池子里扔了一颗小石子,结果所有连着池子的水管都得换标签。
我们在本地做了个最小复现,就新增一个小 bundle,结果 397 个 bundle 字节没变、文件名全变了。比生产那次(296 个)还狠。
怎么修
思路很简单:哈希里别算传递闭包了,只算直接依赖。
一个 bundle 的文件名,应该只由「它自己的内容」+「它直接依赖的那些 bundle」决定。C 变了那是 C 自己的事,不该连累 A。
具体做法是内嵌(fork)一下 Unity 的 Scriptable Build Pipeline 包,改一个文件:
- 把
com.unity.scriptablebuildpipeline从包缓存里拷到Packages/目录下,变成可以改源码的内嵌包。 - 在
ArchiveAndCompressBundles.cs里,新增一个「只算直接依赖、不递归」的方法。 - 让算文件名的哈希用这个新方法,而 manifest 里的依赖信息还是保持原来的传递闭包不变(这样不影响运行时加载)。
改动拢共二十来行,核心就是上面那个思路。细节都写在《Bundle热更重命名级联机制修复.md》里了,这里不展开。
光改完不算数,得验过才敢上
这种动构建链路的事,改完直接发生产那是作死。我们老老实实跑了三步测试,每一步都过了才敢说"可用"。
第一步:先在本机把问题复现出来
改代码之前,先证明"病真的存在"。
- 打一次 bundle 包,存好产物。
- 随便改一个小资源(比如新建一张图,塞进 PackSeparately 组,再让 OtherData 这种高扇入的 hub 引用它)。
- 再打一次包。
- 对比两次产物:数一下有多少个 bundle「内容一样、文件名变了」。
结果:新增 1 个 bundle,397 个 bundle 字节没变但改名。问题复现成功,基线拿到了。
第二步:加入修复代码,再做同样的事
- 内嵌 SBP,改哈希口径。
- 重复第一步的动作:打包 → 改文件 → 再打包。
- 对比两次产物。
结果:还是新增 1 个 bundle,但「字节没变、文件名变」的数量——0。
只剩两个变化:那个真正被改了内容的 bundle(它内容变了,改名是应该的),和那个新加的 bundle。其余几百个 bundle 纹丝不动。
到这里,本机层面就说明修复有效了。
第三步:上生产环境,在真实节点之间验证
本机复现通过还不够,得用之前真实出过问题的 apk 和热更节点来验证,这样才有说服力。
- 从当时的 apk 节点切个分支出来,把修复代码合进去,打一个 apk(作为基线)。
- 再合并最新的生产节点,打一个热更包(里面就是那次会触发大规模级联的更新内容)。
- 对比这份 apk 和这份热更的
manifest.json。
结果:
| 项 | 数量 |
|---|---|
| 完全没动的 bundle | 537 |
| 内容没变但改名的 bundle | 0 |
| 内容真变了的 bundle | 1 |
| 纯新增的 bundle | 1 |
换句话说,那次之前会炸出几百个"假更新"的热更,现在打出来,真正有变化的就俩 bundle,一个是内容真改了,一个是真新增的。剩下的 537 个一个字节没动、文件名也没动。
三步全过,这才能拍胸脯说修好了。
最后提醒一句
改完哈希口径后,第一次打包,所有 bundle 的文件名都会变一次(因为算法变了)。这是预期内的一次性代价,别慌,也别把它单独发成一次热更——要跟一个大的版本更新一起发,让玩家跟着版本整体更新一次就完了。
之后再做热更,就再也不会出现"改一张图、重下几百个 bundle"的鬼故事了。

浙公网安备 33010602011771号