资源热更这块最大的沟通成本不在实现,而在名词。同一个词在不同框架里指不同文件,同一个文件在不同框架里叫不同名字:Addressables 叫 Catalog,YooAsset 里没有 Catalog 这个类却有一份 BuildinCatalog;
Manifest既可能是框架的运行时清单,也可能是 Unity 构建过程产出的一个副产物;而 DLC 干脆不是技术名词,是发行名词。这篇文章不按框架 API 组织,而是按名词组织:每个词先给一句话定义、再给它在文件系统里的落点、最后给"怎么验证它"。目标是下次看到别人文档里的一个词,能立刻判断它说的是哪一层,需要打开哪个文件。
目录
| 章节 | 内容 |
|---|---|
| 先分清三套版本号 | App 版本、资源版本、代码版本,混在一起就永远对不上 |
| Package:资源包 | 它不是安装包;划分边界决定了后面所有能力的上限 |
| Catalog:清单目录 | 三种语境下同名不同物,以及它的两种粒度 |
| Manifest:清单字段逐项 | AssetList / BundleList 每个字段的用途与坑 |
| Bundle:物理容器 | BundleName 与 FileName 的区别,依赖是双向的 |
| DLC:可下载内容 | 为什么它不是技术概念,以及工程上怎么落地 |
| PlayMode 与资源的三层位置 | 内置 / 沙盒 / 远端,以及各自的清理语义 |
| 代码热更的名词 | 热更 DLL 与 AOT 补充元数据,两个 DLL 分工完全不同 |
| 速查词表 | 中英对照总表,可直接当交接文档用 |
| 怎么查:实操路径 | 定位资源在哪个包、确认运行时用的哪个版本 |
| 常见误解 | 一整张"以为是 A 其实是 B"的对照表 |
| 落地清单 | 接入新框架时要确认的问题 |
先分清三套版本号
所有资源热更的排障,第一步都是"你到底在比哪个版本号"。这三套版本号的生命周期、比较方、更新代价都不一样。
| 版本 | 典型字段 / 载体 | 谁负责比较 | 更新手段 | 需要重新出包吗 |
|---|---|---|---|---|
| App 版本 | 原生 versionName / versionCode、强更配置 |
启动时服务端下发 | 应用商店覆盖安装 | 是 |
| 资源版本 | PackageVersion、res_version.txt 里的整数 |
客户端与远端清单比对 | 资源热更 | 否 |
| 代码版本 | 热更程序集文件(如 hlwd.game.bytes) |
随资源版本一起走 | 资源热更 | 否 |
三条必须记住的结论:
- 代码热更不是独立通道。 热更 DLL 就是被打进 Bundle 的一个资源,它走的是和贴图、预制体完全相同的版本比对和下载流程。所以"代码版本"实际是"资源版本"的一个子集,不要把它当第四套版本号去设计流程。
- 资源版本和 App 版本不是包含关系,是两条平行线。 一个 App 版本可以对应任意多个资源版本;但资源版本不能向下兼容到比它更老的 App 版本,除非你主动做了降级逻辑。
- 比较动作只有"相等 / 不等",不要设计成大小比较。 资源版本用来寻址 CDN 目录(换版本换目录),不是用来判断"用户是不是落后了"。想做灰度、回滚,就换一份清单指向,而不是去比数字大小。
为什么先讲这个:后面所有名词——Package、Catalog、Manifest、Bundle——都存在"每个版本一份"的情况。分不清版本号,就会拿 A 版本的清单去解释 B 版本的报错。
Package:资源包
一句话定义:一组资源的集合,拥有独立的清单、独立的版本号、独立的下载器与独立的缓存目录。
最容易搞混的一点:Package 不是安装包。 安装包叫 APK / AAB / IPA,项目里通常叫"包体";Package 是工程内部对资源的逻辑切分,一个安装包里可以有多个 Package。
一个 Package 到底"独立"了什么
| 维度 | 是否独立 | 说明 |
|---|---|---|
| 清单文件 | ✅ | 每个 Package 一套,互不影响 |
| 版本号 | ✅ | 可以单独涨版本,其他包不动 |
| 下载任务 | ✅ | 可以有各自的并发与超时策略 |
| 缓存目录 | ✅ | 可以单独清理、单独校验 |
| 引用计数 | ✅ | 跨包引用需要显式依赖声明,不会自动共享 |
| CDN 目录 | ✅ | 通常按 v{版本}/{包名}/ 分层 |
划分边界的常见口径
| 切分维度 | 适用场景 | 代价 |
|---|---|---|
| 主资源 / 非主资源 | 控制首包体积 | 首启需要一次完整清单比对 |
| 语言 | 多语言发行,按系统语言只下次需要的 | 文案常被其他资源引用,容易跨包 |
| 活动 / 赛季 | 活动内容独立上下线、独立回滚 | 活动数量多时清单数量爆炸 |
| DLC / 付费内容 | 按需购买后下载 | 需要处理授权与卸载 |
| 清晰度 / 画质档 | 高端机下高清资源 | 同一资源存在多份,跨档不能复用 |
| 平台 | 桌面与移动格式不通用(如 BC 与 ASTC) | 这是硬约束,基本必须分 |
划分边界是架构决策,不是"顺手分个组"。 一旦按某个维度切了 Package,跨越这个边界的资源引用就会变成"跨包依赖"——它会带来加载时序、卸载粒度、清单复杂度三方面的额外成本。新项目建议先只切"必须切的"(平台、首包/非首包),把 DLC 这类业务维度留到有明确需求时再加。
Catalog:清单目录
这是本文最容易读错的一个词,因为三种语境下都叫 Catalog,但不是同一个文件。
| 语境 | 名字 | 实际文件 | 回答什么问题 |
|---|---|---|---|
| Addressables | Content Catalog | catalog.json、catalog.hash、catalog_{时间戳}.json |
有哪些资源、地址是什么、在哪个 bundle、依赖谁 |
| YooAsset | 没有 Catalog 类,由两个文件分担 | BuildinCatalog.json、{Package}_{版本}.json |
内置带了哪些 bundle;资源到 bundle 的映射 |
| Unity 原生 | AssetBundleManifest |
OutputCache.manifest(构建副产物) |
bundle 之间的依赖关系 |
通用语义:catalog 是"目录",而且是两级的
抛开框架,catalog 这个词的本义是目录。而在资源框架里它有两个粒度,两者常被混为一谈:
- 清单的清单(对应
BuildinCatalog):包里内置了哪些 Bundle、当前有哪些清单版本可用。它是"找清单"的入口。 - 资源的目录(对应
Manifest/ Addressables Catalog 的主体):某个资源地址 → 它属于哪个 Bundle → 它依赖哪些 Bundle。它是"找资源"的入口。
所以当有人说"catalog 不对",你要追问的是:是入口清单不对,还是资源映射不对? 这两个问题的排查路径完全不同。
一份真实的内置清单结构
{
"FileVersion": "1.0.0",
"PackageName": "DefaultPackage",
"PackageVersion": "12",
"Wrappers": [
{
"BundleGUID": "531607a7cd2af3b8a31565ec3f2d374c",
"FileName": "defaultpackage_assets_gamemain_scenes_531607a7cd2af3b8a31565ec3f2d374c.bundle"
}
]
}
读这份文件的正确姿势是倒着读:先看 PackageVersion(当前内置到第几个版本),再看 Wrappers 里每个 FileName 是否真的存在于磁盘上。出现"清单说有这么个包,但磁盘上没有"的时候,问题就锁定在内置产物不完整,而不是下载逻辑。
hash 文件的作用
清单旁边通常有一个同名 .hash 文件(或清单里带 Hash 字段)。它的唯一用途是:让客户端在不下载整个清单的情况下,判断清单有没有变。
- 只下 hash(极小的文件)→ 和本地比对 → 相同就跳过清单下载 → 直接进下载阶段。
- 不同才下完整清单。
这就是"每次启动都要检查更新,但流量消耗很小"的实现基础。反过来说,如果 hash 的生成逻辑和清单内容不匹配(例如清单被重新序列化但 hash 没重算),会出现永远认为需要更新的死循环。
必须记住的坑:BundleID 是数组下标
清单里代表 Bundle 的字段在很多框架里是整数 ID,它不是稳定标识符,而是同一份清单内 BundleList 数组的下标。
# 给定一个 bundleID,反查是哪个 bundle
$manifest = Get-Content .\DefaultPackage_12.json -Raw | ConvertFrom-Json
$bundleId = 15
$bundle = $manifest.BundleList[$bundleId]
$bundle.BundleName
由此推出两条结论:
- 这个 ID 一旦清单重建就会变,不能持久化、不能跨版本比较、不能写死在配置表里。
- 排查时要拿同一份清单内部的 ID 去查,不要拿 A 版本的报错 ID 去 B 版本清单里找,那样必然对不上。
- 反查"这个资源依赖了谁"的正确方式是读
DependBundleIDs(一组下标),再逐个映射回BundleList。人眼读 JSON 很痛苦,优先用构建报告(见下节)。
Manifest:清单字段逐项
Manifest 是"资源目录"的本体。不同框架字段名不同,但结构高度一致:一张资源表 + 一张容器表,用 ID 互相引用。
以 YooAsset 的 PackageManifest 为例:
AssetList(资源表)
| 字段 | 含义 | 注意事项 |
|---|---|---|
Address |
逻辑地址,业务层加载时用 | 默认用资源路径;配了可寻址规则后可能变成短名 |
AssetPath |
工程内路径 | 与 Address 同时存在时,需明确哪个是"对外契约" |
BundleID |
所属 Bundle | 是下标,见上一节 |
DependBundleIDs |
依赖的 Bundle 集合 | 一组下标;共享资源会在这里指向 share 包 |
Tags |
标签 | 用于按标签批量下载、预加载 |
BundleList(容器表)
| 字段 | 含义 | 注意事项 |
|---|---|---|
BundleName |
逻辑名 | 相对稳定,会体现打包目录结构 |
FileName |
物理名 | BundleName_HashName.bundle,含内容 Hash |
FileHash |
内容 Hash | 判断"这个包的内容变没变"的唯一依据 |
FileCRC |
校验值 | 下载完整性校验 |
FileSize |
字节数 | 用于下载进度与体积预算 |
DependBundleIDs |
依赖的其他 Bundle | 加载一个包会先递归加载它 |
读法建议:别读 JSON,读构建报告
原始 JSON 适合程序读,不适合人读。构建过程通常会额外产出一份可读报告,包含四个部分:
| 报告区块 | 内容 | 什么时候看 |
|---|---|---|
| Summary | 整体统计:资源数、包数、总大小 | 发现包体异常膨胀时 |
| AssetInfos | 每个资源:路径、所属包、依赖包 | 查"这个资源被谁引用" |
| BundleInfos | 每个包:逻辑名、物理名、依赖包、被谁依赖、包含哪些资源 | 查共享包、查环形依赖 |
| IndependAssets | 没有任何包引用、也没被引用的独立资源 | 查无用资源、查漏加入收集器的资源 |
其中 BundleInfos 里的 ReferenceBundles(被谁依赖)是最有价值的一列。原始 JSON 只存了"我依赖谁"(正向),要回答"谁依赖我"必须遍历全表构建反向索引——报告已经帮你算好了。
判断一个共享包是否健康,就看它的 ReferenceBundles 数量:只有 1 个引用者的"共享包",说明拆包规则切错了,它白白多了一次 IO 和一份清单开销。
Bundle:物理容器
Bundle 是磁盘上真实存在的文件,前面所有概念最终都要落到它身上。
两个名字,别混用
| 名字 | 形态 | 稳定性 | 用途 |
|---|---|---|---|
BundleName |
defaultpackage_assets_gamemain_materials |
相对稳定 | 日志、依赖分析、人工排查 |
FileName |
defaultpackage_assets_gamemain_materials_abc123....bundle |
内容一变就变 | 磁盘文件名、CDN 路径、缓存 key |
为什么要带 Hash 后缀:因为要支持"同一路径下新旧内容共存"。如果文件名固定,CDN 缓存会给你旧文件;CM 部署新版本时也无法灰度。带 Hash 后,abc123 和 def456 是两个不同 URL,缓存互不干扰,代价是旧文件需要清理策略。
依赖是双向的
清单里存的是正向依赖(我依赖谁),但排查时经常需要反向信息(谁依赖我):
- 共享资源的判定:一个材质被 10 个预制体引用 → 抽成 share 包 → 这 10 个包都会在
DependBundleIDs里指向它。 - 卸载安全:卸载 A 包前要确认没有其他已加载包依赖它,否则会出现"资源凭空消失"。
- 环形依赖:A 依赖 B、B 又依赖 A,加载时死锁。这类问题在正向清单里很难看出来,看报告的反向列表反而一目了然。
命名规则会泄漏架构
BundleName 通常由"打包目录 + 资源类型"拼出来,例如:
defaultpackage_share_assets_gamemain_materials
^^^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^^^^^^^^^^
包名前缀 标记 目录路径 资源类型
所以看一个项目的 BundleName 分布,基本就能推断出它的打包策略:出现大量 share 前缀说明启用了共享包规则;目录层级很深说明按目录打包;同名资源出现在多个包名里说明收集器配置有重叠。
DLC:可下载内容
DLC(Downloadable Content)是发行与商业概念,不是技术概念。
这句话是理解 DLC 的关键。在资源框架里你找不到任何叫 DLC 的 API——因为 DLC 描述的是"这个东西在产品上怎么卖、怎么交付",而技术侧只提供"按需下载一组资源"这个能力。把 DLC 当技术名词去找,就会一直在文档里找不到答案。
DLC 与相邻概念的边界
| 概念 | 属于哪个维度 | 回答的问题 | 典型关注点 |
|---|---|---|---|
| DLC | 发行 / 商业 | 主包之外,用户按需购买或获取的内容单元是什么 | 定价、授权、上下架、评级 |
| 热更(Patch) | 交付机制 | 已发布的内容怎么替换成新版本 | 版本比对、差异下载、回滚 |
| 分包(Split) | 包体结构 | 首包放什么、其余按什么边界拆出去 | 首包体积、下载时机 |
| Package | 技术实现单元 | 实现上述目标时,资源按什么边界分组成独立清单 | 清单数量、跨包依赖 |
一句话概括四者关系:DLC 是一类需求,分包是实现它的包体结构策略,Package 是落地时的技术单元,热更是让它们能被持续更新的机制。
技术侧怎么落地一个 DLC
既然没有 API,落地方式就是组合既有能力,通常需要同时满足下面几条:
- 独立 Package:DLC 内容单独一个 Package(或一组),独立清单、独立版本号——这样才能"用户没买就不下载"。
- 独立下载入口:不进入默认启动下载流程,只在用户触发时(购买成功、点了某个入口)才开始下载。
- 两道门:可达 + 授权。技术上"资源下载完了"和"用户有权访问"是两件独立的事。很多线上事故是资源已经能加载,但授权状态没同步,于是未购买用户通过其他入口意外加载到了内容。不要用"资源没下载"来当权限校验。
- 卸载与回收:DLC 是第一个真正需要"用户主动卸载"的场景,要能清理清单与缓存,且不影响主包。
- 回滚与下架:DLC 下架时,已经买过的用户要能继续用(点对点服务),没买过的不能再看到入口。这要求版本清单支持"指向旧清单"。
- 依赖交叉:DLC 与主包互相引用时,谁先加载?主包更新后 DLC 的清单是否过期?这是 DLC 方案里最容易出问题的一环。
平台差异(同一套技术,不同的约束)
| 平台 | 主要约束 | 对 DLC 的影响 |
|---|---|---|
| Android(AAB) | 有分片机制,但资源分片与资源配置是两套东西 | 原生分片不能替代资源 DLC 方案 |
| iOS | 审核对"下载后可改变功能"的内容敏感 | 内容形态与文案要提前对齐审核口径 |
| PC / 主机商店 | 有正式的 DLC SKU 与授权体系(Entitlement) | 商业侧的 DLC 与技术侧的资源包要建立映射表 |
| 微信小游戏等 | 有明确的分包数量与体积上限 | “DLC” 实际是分包能力的重新组合,不能照搬移动端结论 |
结论:DLC 方案 = 发行策略 + 包体结构 + 资源框架能力三者的组合。 三方任何一方没对齐,都会出现"技术做完了但商业侧不理解"或"商业卖完了但技术下不下去"的情况。
PlayMode 与资源的三层位置
同一份资源在运行时可能来自三个地方,PlayMode 决定它们的优先级和组合方式。
| 模式 | 资源来源 | 典型用途 | 关键差异 |
|---|---|---|---|
| Editor 模拟模式 | 直接读 AssetDatabase | 日常开发 | 不走 Bundle,加载行为与真机不同 |
| 离线模式 | 只读内置(StreamingAssets) | 单机、无网、首包自包含产品 | 完全不请求远端,也无法热更 |
| 联机模式(HostPlay) | 内置 + 沙盒缓存 + 远端 | 绝大多数热更产品 | 三层按优先级查找,沙盒覆盖内置 |
| Web 模式 | 远端 HTTP 直读 | 小游戏、WebGL | 无本地缓存概念,依赖浏览器缓存 |
三层位置与清理语义
| 位置 | 物理路径 | 内容 | 谁会清它 |
|---|---|---|---|
| 内置 | StreamingAssets 下 |
随包发布的清单与 Bundle | 只能靠覆盖安装更新 |
| 沙盒 | persistentDataPath 下 |
热更下载下来的清单与 Bundle、版本记录 | 应用自身策略;覆盖安装/重装会清 |
| 远端 | CDN,通常 v{版本}/{包名}/ |
权威清单与 Bundle | 由 CM 部署流程控制 |
这里面有两个必须理解的行为:
- 沙盒的生效方式是把内置清单"复制"过来再更新。 首启时如果沙盒为空,框架会把内置清单复制进沙盒,然后在沙盒那份上继续做增量更新。这解释了为什么首启离线也能跑——它读的是内置产物,而不是远端。
- 覆盖安装会清沙盒。 用户覆盖安装新 App 版本后,沙盒里的清单与 Bundle 可能被清掉,于是回到"用内置清单 + 重新下载"。如果新 App 版本内置的是老清单,用户就会先回退到老版本资源,再更新上去——这段窗口期是线上 bug 的高发期,尤其是"覆盖安装后必须重启一次才正常"这类问题。
版本记录文件
热更流程通常会在沙盒里留一个极小的版本文件(例如一行整数),记录"上次成功更新到哪个资源版本"。
- 它的作用是下次启动的目标基线,避免每次都从头比对清单。
- 它必须在下载全部成功之后才写入。写早了会出现"版本号是新的,资源还是旧的"这种最难查的状态:清单说资源都在,实际加载的是旧内容。
- 它和远端不一致时,不要直接报错退出,而是重新走一次完整比对,这是最常见的自愈路径。
代码热更的名词
代码热更会额外引入几个词,其中最容易被混为一谈的是:热更 DLL 和 AOT 补充元数据。两者都是 .bytes 文件、都放在资源里、都由同一份清单管理,但职责完全不同。
| 名词 | 是什么 | 数量 | 何时需要更新 |
|---|---|---|---|
| 热更程序集 | 被编译成热更 DLL 的业务代码(Hotfix 层) | 通常 1 个 | 业务逻辑改动时 |
| AOT 补充元数据 | AOT 程序集在热更代码里被泛型实例化时需要的元数据 | 一组 | AOT 层改动、或热更代码新用了 AOT 泛型时 |
| link.xml | 告诉裁剪器"这些类型/方法不要裁掉" | 1 个 | 反射、序列化、泛型被裁剪导致运行时才报错时 |
| AOT 泛型引用收集产物 | 构建时扫出来的"热更代码实际用到的 AOT 泛型组合" | 1 个 | 与 AOT 补充元数据配套 |
关键推论:
- 只要热更代码新用到一个 AOT 泛型组合,就必须更新补充元数据。 这是"代码热更上去后,只有某个功能崩溃,报
ExecutionEngineException/Attempting to call method ... for which no ahead of time code was provided“这类问题的根因。 - AOT 层改动无法热更。 它只存在于 App 版本里。所以架构上要把"AOT 尽可能薄"当作硬约束——AOT 越厚,需要强更的改动就越多。
- 加载顺序必须是先补元数据、后加载热更程序集。顺序反了会在加载阶段就失败。
速查词表
| 名词 | 英文 / 常见写法 | 一句话定义 | 常见落点 |
|---|---|---|---|
| 资源包 | Package | 一组资源的集合,独立清单 + 独立版本号 | 框架名称空间下的 Package 对象 |
| 清单目录 | Catalog | “目录"的总称;两级粒度见正文 | catalog.json / BuildinCatalog.json |
| 内置清单 | BuildinCatalog | 包里内置的 Bundle 与清单版本索引 | StreamingAssets 下 |
| 资源清单 | Manifest / PackageManifest | 资源地址到 Bundle 的映射表 | {Package}_{版本}.json |
| 摘要文件 | .hash |
用来快速判断清单是否变化 | 清单同目录 |
| 资源 | Asset | 最小的加载单位 | 工程内资源路径 |
| 地址 | Address / Location | 业务层加载时用的逻辑名 | 清单的 Address 字段 |
| 容器 | Bundle / AssetBundle | 磁盘上的物理包文件 | *.bundle |
| 逻辑名 | BundleName | 相对稳定的包名 | 清单的 BundleName 字段 |
| 物理名 | FileName | 带内容 Hash 的真实文件名 | CDN 路径、本地缓存 |
| 共享包 | Share Bundle | 被多个包共同依赖的包 | BundleName 含 share 标识 |
| 正向依赖 | DependBundles | 我依赖谁 | 清单的 DependBundleIDs |
| 反向依赖 | ReferenceBundles | 谁依赖我(需构建反向索引) | 构建报告的 BundleInfos |
| 可下载内容 | DLC | 主包之外按需获取的内容单元(发行概念) | 无 API,用独立 Package 落地 |
| 热更 | Patch / HotUpdate | 把已发布内容替换为新版本的机制 | 版本比对 + 下载 |
| 分包 | Split | 首包与其余内容的包体划分策略 | 收集器与打包规则 |
| 首包 | Buildin / Base | 随安装包一起发出的内容 | StreamingAssets |
| 沙盒 | persistentDataPath / Sandbox | 热更内容的本地落地目录 | 可写目录 |
| 播发模式 | PlayMode | 决定资源来源组合的运行模式 | 框架初始化参数 |
| 内容校验 | CRC / Hash | 下载完整性与内容变更判定 | 清单字段 |
| 构建报告 | Report | 人可读的构建产物说明 | *.report |
| 强更 | Force Update | 必须覆盖安装才能继续使用 | App 版本号比对 |
| 授权 | Entitlement | 用户是否有权访问某内容 | 服务端 / 商店侧 |
怎么查:实操路径
名词认清了,接下来是"遇到问题怎么定位”。这一节给的是从现象到文件的路径。
想知道"某个资源在哪个包里”
- 打开构建报告,在
AssetInfos里搜资源路径 → 拿到所属BundleName。 - 在
BundleInfos里查这个BundleName→ 看它依赖谁、被谁依赖、包含哪些资源。 - 只有拿不到报告时,才回到原始清单里做下标映射。
想知道"这个包为什么这么大"
- 看报告
Summary的体积分布,先确定是"包多"还是"单包大"。 - 单包大 → 在
BundleInfos里看它的BundleContents是否有意外的大资源(图集、音频、视频)。 - 包多 → 检查收集器规则是否有重叠;
IndependAssets是否有本该合并的资源。
想知道"运行时实际用的是哪个版本"
| 查什么 | 怎么查 |
|---|---|
| 当前资源版本 | 读沙盒里的版本记录文件;或框架的 GetPackageVersion() |
| 清单到底加载的是哪一份 | 对比内置清单与沙盒清单的版本号 |
| 资源是从内置还是远端来的 | 看缓存命中日志;或临时禁用远端,观察是否还能加载 |
| Bundle 的真实内容 | 用二进制工具列出 Bundle 内的资源名清单,与报告对照 |
一个通用原则
所有资源问题的证据链都必须落在"文件 + 版本"上。 只看日志会出现"日志说下载成功了,但加载的是旧资源"这种无法收敛的状态;必须同时确认哪一份清单、哪一个版本、哪个物理文件。这也是前面反复强调"分不清版本号就永远对不上"的原因。
常见误解
| 以为是 A | 实际是 B | 后果 |
|---|---|---|
| Catalog 就是 Manifest | Catalog 是"目录"的总称,Manifest 是其中"资源映射"这一级 | 排查时找错文件 |
| Manifest 只有一个 | 每个 Package、每个版本各一份 | 拿错版本的清单去解释报错 |
| BundleID 是稳定 ID | 它是同一份清单内数组的下标 | 持久化后跨版本必然失效 |
| DLC 是技术名词 | 它是发行概念,技术侧只有"按需下载一组资源" | 在框架文档里找不到答案 |
| DLC 下载完了就等于有权访问 | 可达性与授权是两道独立的门 | 权限绕过,未购买用户加载到内容 |
| 热更 DLL 和 AOT 补元数据是一回事 | 分工完全不同,后者只在特定条件下才需要更新 | 热更后局部功能崩溃 |
| 代码热更有独立通道 | 热更 DLL 本身就是资源,走同一条链路 | 设计出多余的第二套版本体系 |
| Bundle 压缩能省内存 | 容器压缩省磁盘与下载,不省运行时内存 | 内存优化方向选错 |
| 覆盖安装后一切从头开始 | 沙盒被清 → 先回退到内置清单再更新 | “覆盖安装后必须重启一次"类问题 |
| 版本号可以比大小 | 它用于寻址目录,只该做相等判断 | 灰度与回滚逻辑写歪 |
落地清单
接入或接手一套资源热更框架时,把下面这些问题问清楚,基本就能对齐所有名词:
- 这个项目的资源版本号存在哪个文件?谁负责写入?写入时机是在下载前还是下载后?
- 一共划分了哪些 Package?切分维度是什么?有没有跨包引用?
- Catalog 的入口文件是哪一个?它和资源映射清单分别叫什么名字?
- 清单是否带 hash 摘要?更新检查是先比摘要还是直接下清单?
- BundleID 是下标还是稳定标识?项目里有没有把它当稳定 ID 用错的地方?
- 有没有可读的构建报告?
ReferenceBundles是否用于审查共享包? - 是否存在只有一个引用者的共享包?(拆包规则切错的信号)
- DLC / 活动内容是用独立 Package 落的,还是混在主包里用标签控制?
- DLC 的授权校验在哪里做?有没有"资源可达即视为有权"的隐患?
- 三个资源位置(内置 / 沙盒 / 远端)的清理策略分别是什么?覆盖安装会怎样?
- 热更 DLL 与 AOT 补充元数据是否都进了清单?更新条件是否明确?
- 有没有回滚路径?回滚时是换清单还是换版本号?
一句话总结
资源热更的名词可以归到四层上:Package 是边界,Catalog / Manifest 是地图,Bundle 是实物,DLC 是需求。
- Catalog 和 Manifest 不是同义词:Catalog 是"目录”,Manifest 是目录里"资源映射"那一级;框架不同名字还不一样,先确认对方说的是哪一层再讨论。
- BundleID 是下标不是 ID,所有"跨版本对不上"的问题多半源于此。
- DLC 不是技术概念,技术侧只有"按需下载一组资源";它真正难的地方在授权、卸载、回滚、依赖交叉,而不是下载。
- 三套版本号必须分开看,代码热更不是第四套通道,它本身就是资源。
- 所有结论都要落到文件 + 版本 + 物理路径三件事上,只凭日志无法收敛。
相关阅读:Unity 开发高级/资深 03:资源管理与热更新、Unity 图片资源格式与内存、Unity 开发高级/资深 技能总览。
评论