1. 为什么我要从路径加载迁移到可寻址模式
做过Unity资源管理的人大概都有这种体会:项目初期用Resources或者直接路径加载,代码写起来飞快,Resources.Load("Prefabs/UI/MainPanel")一行搞定,谁用谁知道。但项目一旦膨胀到几百个预制体、上千张贴图,这套玩法就开始反噬了。我接手过一个中型项目,热更需求一上来,路径加载的硬伤全暴露了——资源目录一变,代码里散落各处的字符串路径全得改,打包时还得手动维护AssetBundle清单,稍不留神就漏掉某个依赖,线上直接白屏。
YooAsset的可寻址模式(Addressable)就是冲着这些痛点来的。它把“资源在哪里”和“资源叫什么”彻底解耦,你不再关心文件放在哪个文件夹,只需要给资源分配一个地址,加载时用地址去取。这跟Unity官方的Addressable系统思路类似,但YooAsset更轻量,对国内项目的热更流程适配得更顺手,尤其是配合微信小游戏、Pico这类平台时,资源分包和远程加载的配置要直观得多。
这篇文章适合两类人看:一类是正在用YooAsset路径加载模式、被资源路径管理折磨得够呛想迁移到可寻址模式的;另一类是刚接触YooAsset,想直接上可寻址模式但不知道从哪下手的。我会把迁移过程中踩过的坑、参数怎么配、代码怎么改、打包策略怎么选,全部拆开讲清楚。你不需要有Addressable的使用经验,但最好对YooAsset的基本概念(比如Package、PlayMode、资源收集器)有个大概了解,不然有些术语会卡住。
先说结论:迁移本身不难,难的是迁移后资源加载逻辑的重构和打包策略的重新设计。我见过不少人改完代码发现资源加载不出来了,或者热更包体积暴涨,问题基本都出在这两块。下面我按实际迁移的顺序,从设计思路到代码实操再到问题排查,一步步来。
2. 迁移前的整体设计与思路拆解
2.1 路径加载和可寻址模式的本质区别
路径加载的核心逻辑是“我知道资源在哪个文件夹,我直接去那个文件夹拿”。代码里到处都是"Assets/GameRes/UI/MainPanel.prefab"这样的字符串,资源收集器按文件夹规则打包,加载时通过路径定位资源。这种模式在单机项目或者资源量小的项目里没问题,但一旦涉及热更,路径就成了最大的不稳定因素——你改了文件夹结构,所有引用这个路径的代码全得跟着改,而且路径字符串没有编译期检查,写错了只有运行时才知道。
可寻址模式的逻辑是“我给资源起个名字,加载时用名字去查表”。这个“名字”就是地址(Address),它和资源的物理路径完全解耦。资源收集器负责把物理路径映射到地址,加载时通过LoadAssetAsync<GameObject>("MainPanel")这样的方式获取。地址可以随便改,只要收集器里的映射关系同步更新就行,代码层面完全不用动。更重要的是,地址可以跨Package使用,这意味着你可以把UI资源放在一个Package,场景资源放在另一个Package,加载时统一用地址访问,不用关心它们各自在哪个包里。
我画个简单的对比表,方便你直观感受:
| 对比维度 | 路径加载模式 | 可寻址模式 |
|---|---|---|
| 资源定位方式 | 物理路径字符串 | 逻辑地址 |
| 路径变更影响 | 所有引用处需修改 | 仅收集器配置需修改 |
| 编译期检查 | 无 | 无(但地址可集中管理) |
| 热更支持 | 需手动维护清单 | 内置清单版本管理 |
| 跨Package访问 | 不支持 | 支持 |
| 加载代码可读性 | 路径长,易写错 | 地址短,语义清晰 |
| 资源去重 | 依赖收集器规则 | 地址唯一性保证 |
这个表里最关键的是“跨Package访问”和“资源去重”。路径加载模式下,如果两个Package都引用了同一张贴图,打包时这张贴图会在两个包里各存一份,包体积直接翻倍。可寻址模式下,你可以给这张贴图分配一个全局地址,两个Package都通过这个地址加载,YooAsset会自动处理依赖,只存一份。
2.2 迁移的时机选择和风险评估
不是所有项目都适合立刻迁移。我建议你先评估三个指标:资源总量、热更频率、团队规模。资源总量超过500个文件、热更频率高于每周一次、团队超过3个人同时改资源相关代码,这三个条件满足两个,迁移的收益就明显大于成本。反之,如果项目已经临近上线,资源结构稳定,热更需求不强,强行迁移反而可能引入新问题。
迁移的风险主要集中在三块:一是加载代码的重构量,路径加载的代码通常散落在各个业务模块里,迁移时需要逐个替换;二是打包策略的重新设计,可寻址模式下资源收集器的规则和路径模式完全不同,需要重新规划;三是热更清单的兼容性,如果线上已经有旧版本的路径模式清单,迁移后需要处理版本兼容,否则老用户更新会出问题。
我的建议是分阶段迁移:先在一个独立模块(比如UI模块)试点,把该模块的资源改成可寻址模式,验证加载、打包、热更全流程没问题后,再逐步推广到其他模块。这样风险可控,出问题也能快速回滚。
2.3 可寻址模式的核心概念梳理
在动手之前,有几个概念必须搞清楚,不然后面配置收集器时会一头雾水。
资源收集器(Asset Collector)是可寻址模式的核心。它负责扫描指定目录下的资源,按照你设定的规则给每个资源分配地址。YooAsset提供了几种收集器类型,最常用的是“收集器目录”(Collector Directory),你指定一个文件夹,它会把文件夹下所有资源都收集进来,并按照“地址规则”生成地址。地址规则可以配置成“文件名”或者“相对路径”,我一般用“文件名”,因为地址越短越好记。
可寻址资源(Addressable Asset)是收集器扫描后生成的资源条目,每个条目包含物理路径、地址、标签、分组等信息。你可以在收集器面板里手动修改某个资源的地址,也可以批量设置。地址必须全局唯一,如果两个资源分配了相同的地址,打包时会报错。
资源分组(Group)是可寻址模式下的打包单位。一个分组对应一个AssetBundle文件,分组内的资源会打在一起。分组的粒度直接影响包体积和加载效率——分得太细,Bundle数量多,加载时的IO次数增加;分得太粗,单个Bundle体积大,更新时下载量大。我一般按业务模块分组,比如UI一个组、角色一个组、场景一个组,每个组控制在2-5MB左右。
清单文件(Manifest)是热更的核心。可寻址模式下,YooAsset会为每个Package生成一份清单,记录所有资源的地址、依赖关系、Hash值、Bundle归属等信息。热更时对比本地清单和远程清单的差异,只下载变化的Bundle。清单文件本身也会被打包,所以迁移后第一次热更需要确保远程清单已经更新。
3. 核心细节解析与实操要点
3.1 资源收集器的配置细节
打开YooAsset的资源收集器面板(Window -> YooAsset -> Asset Collector),你会看到左侧是Package列表,右侧是收集器配置区域。迁移的第一步就是在这里创建新的收集器。
点击“添加收集器”,选择“收集器目录”,然后指定要收集的文件夹。这里有个关键点:不要直接收集Assets根目录,否则会把所有资源都收进来,包括不需要热更的编辑器资源。我一般会建一个专门的资源根目录,比如Assets/GameRes,所有需要热更的资源都放在这个目录下,收集器只扫描这个目录。
地址规则我选“文件名”,但这里有个坑:如果不同文件夹下有同名文件,地址会冲突。比如UI/MainPanel.prefab和Scene/MainPanel.prefab,两个都叫MainPanel,地址就重复了。解决办法是在地址规则里加上“相对路径”的前缀,或者手动给冲突的资源改地址。我倾向于后者,因为手动改地址可以保持地址的语义清晰,比如改成UI_MainPanel和Scene_MainPanel。
分组规则我按“收集器目录”来分,每个子文件夹自动成为一个分组。这样UI文件夹下的资源自动打成UI组,角色文件夹下的打成角色组。分组名可以手动改,我一般改成有意义的名称,比如Group_UI、Group_Character,方便在代码里引用。
注意:收集器配置修改后,必须点击“保存”按钮,否则下次打开面板配置会丢失。这个坑我踩过好几次,改了半天配置没保存,重新打开全没了。
3.2 资源地址的命名规范
地址命名看起来是小事,但迁移后代码里到处是地址字符串,命名不规范会严重影响可维护性。我总结了一套命名规范,你可以参考:
- 前缀表示资源类型:
UI_表示界面预制体,Char_表示角色资源,SFX_表示音效,Tex_表示贴图 - 中间用下划线连接模块名:
UI_MainPanel、UI_SettingsPanel - 后缀表示变体:
UI_MainPanel_HD、UI_MainPanel_SD
这样命名后,代码里看到LoadAssetAsync<GameObject>("UI_MainPanel")就知道是加载主界面预制体,语义非常清晰。而且按前缀排序后,同类型的资源会聚在一起,方便批量管理。
地址的长度也要控制。我见过有人用完整路径当地址,比如Assets/GameRes/UI/Prefabs/MainPanel.prefab,这跟路径加载没区别,失去了可寻址模式的意义。地址应该尽量短,但又要保证唯一性和可读性。
3.3 加载代码的重构要点
路径加载的代码通常长这样:
var handle = YooAssets.LoadAssetAsync<GameObject>("Assets/GameRes/UI/MainPanel.prefab");迁移到可寻址模式后,改成:
var handle = YooAssets.LoadAssetAsync<GameObject>("UI_MainPanel");看起来只是字符串变了,但背后的逻辑完全不同。路径加载时,YooAsset会根据路径去清单里查找对应的Bundle,然后加载。可寻址模式下,YooAsset会根据地址去清单里查找资源条目,然后根据条目里的Bundle信息加载。地址是逻辑概念,路径是物理概念,这是本质区别。
重构时要注意几个点:一是所有硬编码的路径字符串都要替换成地址,建议用常量类统一管理地址,避免散落在各处;二是加载后的资源释放逻辑要检查,可寻址模式下资源的引用计数管理更严格,忘记释放会导致内存泄漏;三是同步加载和异步加载的接口要区分清楚,可寻址模式下同步加载的限制更多,比如不支持从远程加载。
我一般会封装一个资源加载管理器,对外提供LoadAssetAsync<T>(string address)这样的接口,内部处理Package选择、加载模式判断、引用计数管理。这样业务代码只需要关心地址,不需要关心底层细节。
3.4 打包策略的重新设计
路径加载模式下,打包策略通常很简单:按文件夹打包,每个文件夹一个Bundle。可寻址模式下,打包策略需要重新设计,因为分组的粒度直接影响热更效率和运行时性能。
我的打包策略设计原则是:
- 按业务模块分组:UI、角色、场景、特效、音效各成一组,方便按模块热更
- 控制单组体积:每组控制在2-5MB,超过5MB考虑拆分,小于1MB考虑合并
- 公共资源单独分组:多个模块共用的资源(比如通用贴图、字体)单独放一个公共组,避免重复打包
- 频繁更新的资源单独分组:比如活动配置、UI预制体,这些更新频繁的资源单独成组,减少每次热更的下载量
分组配置在收集器面板里完成,每个收集器目录对应一个分组,你可以在分组设置里调整打包参数,比如压缩方式、是否包含依赖等。压缩方式我一般选LZ4,压缩率适中,加载速度快。如果包体积敏感,可以选LZMA,但加载时需要解压,速度会慢一些。
提示:打包前一定要用“预览”功能检查分组结果,看看每个组里有哪些资源,体积多大。我遇到过分组配置写错,把整个角色文件夹打成一个组,结果单个Bundle超过50MB,热更时用户下载半天。
4. 实操过程与核心环节实现
4.1 环境准备和Package初始化
迁移的第一步是确保YooAsset版本正确。我用的版本是1.5.x,这个版本对可寻址模式的支持比较完善。如果你用的是更早的版本,建议先升级,否则有些API可能不兼容。
Package初始化代码需要调整。路径加载模式下,初始化通常只指定Package名称和PlayMode。可寻址模式下,还需要指定资源收集器的配置。我一般把初始化代码放在游戏启动脚本里:
private IEnumerator InitializeYooAsset() { // 创建Package var package = YooAssets.CreatePackage("GamePackage"); // 设置PlayMode var initParameters = new EditorSimulateModeParameters(); initParameters.SimulateManifestFilePath = EditorSimulateModeHelper.SimulateBuild("GamePackage"); // 初始化Package var initOperation = package.InitializeAsync(initParameters); yield return initOperation; if (initOperation.Status == EOperationStatus.Succeed) { Debug.Log("YooAsset初始化成功"); } else { Debug.LogError($"YooAsset初始化失败:{initOperation.Error}"); } }这段代码在编辑器模式下使用模拟构建,打包后需要改成OfflinePlayModeParameters或HostPlayModeParameters。HostPlayMode用于热更模式,需要指定远程资源地址和清单地址。
4.2 资源收集器的创建和配置
在YooAsset面板里创建收集器的步骤:
- 打开Window -> YooAsset -> Asset Collector
- 在左侧Package列表选择“GamePackage”
- 点击“添加收集器”,选择“收集器目录”
- 在“收集目录”字段填入
Assets/GameRes - 在“地址规则”下拉框选择“文件名”
- 在“分组规则”下拉框选择“收集器目录”
- 点击“保存”
保存后,收集器会自动扫描Assets/GameRes下的所有资源,并在面板里列出。你可以看到每个资源的地址、分组、标签等信息。如果地址有冲突,面板会用红色标记出来,需要手动修改。
我一般会再添加一个“收集器文件”类型的收集器,用于收集一些零散的资源,比如配置表、字体文件。这些资源不适合按目录收集,单独指定文件更灵活。
4.3 加载代码的批量替换
这是迁移过程中工作量最大的部分。路径加载的代码通常散落在各个业务脚本里,我建议用全局搜索的方式逐个替换。搜索关键词是LoadAssetAsync和LoadAssetSync,找到所有调用点,把路径字符串替换成地址。
替换时要注意几个特殊情况:
- 场景加载:路径加载时用
LoadSceneAsync("Assets/GameRes/Scenes/Main.unity"),可寻址模式下改成LoadSceneAsync("Scene_Main"),地址需要在收集器里配置 - 子资源加载:比如加载预制体上的某个组件,路径加载时用
LoadAssetAsync<GameObject>("path").Asset,可寻址模式下一样,只是路径换成地址 - 依赖资源加载:如果资源A依赖资源B,路径加载时需要手动加载B,可寻址模式下YooAsset会自动处理依赖,不需要手动加载
我封装了一个资源加载管理器,把加载逻辑集中管理:
public class ResourceManager { private Dictionary<string, AssetHandle> _handles = new Dictionary<string, AssetHandle>(); public AssetHandle LoadAssetAsync<T>(string address) where T : UnityEngine.Object { if (_handles.TryGetValue(address, out var existingHandle)) { return existingHandle; } var handle = YooAssets.LoadAssetAsync<T>(address); _handles[address] = handle; return handle; } public void ReleaseAsset(string address) { if (_handles.TryGetValue(address, out var handle)) { handle.Release(); _handles.Remove(address); } } }这个管理器做了两件事:一是缓存已加载的资源句柄,避免重复加载;二是提供统一的释放接口,方便管理引用计数。实际项目中还需要处理加载失败、超时、取消等异常情况,这里为了简洁省略了。
4.4 打包和热更流程的调整
打包流程在可寻址模式下有变化。路径加载时,打包只需要指定Package和输出目录。可寻址模式下,打包前需要先构建收集器清单,然后再打包。
打包步骤:
- 在YooAsset面板点击“构建”按钮
- 选择构建模式(强制构建或增量构建)
- 选择输出目录
- 点击“开始构建”
构建完成后,输出目录里会生成清单文件和Bundle文件。清单文件包括PackageManifest_GamePackage.version和PackageManifest_GamePackage.bytes,前者是版本号,后者是清单内容。Bundle文件按分组生成,每个分组一个文件。
热更流程也需要调整。路径加载时,热更通常对比文件列表的差异。可寻址模式下,热更对比的是清单文件的差异。流程是:
- 游戏启动时请求远程清单文件
- 对比本地清单和远程清单的版本号
- 如果版本号不同,下载新的清单文件
- 解析新清单,对比Bundle的Hash值,找出变化的Bundle
- 下载变化的Bundle
- 更新本地清单
YooAsset提供了UpdatePackageManifestAsync和CreateDownloader等API来简化这个流程。我一般会封装一个热更管理器,处理版本对比、下载进度、错误重试等逻辑。
注意:迁移后第一次热更需要确保远程清单已经更新,否则老版本的清单会导致资源加载失败。我建议在迁移完成后,先做一次全量热更测试,确保所有资源都能正确下载和加载。
5. 常见问题与排查技巧实录
5.1 资源加载失败:地址找不到
这是迁移后最常见的问题。现象是LoadAssetAsync返回的句柄状态是Failed,错误信息是“Address not found”。原因通常是收集器配置里没有这个地址,或者地址拼写错误。
排查步骤:
- 打开YooAsset面板,在收集器里搜索这个地址,看是否存在
- 如果不存在,检查资源是否在收集目录下,或者地址规则是否配置正确
- 如果存在,检查代码里的地址字符串是否和收集器里的完全一致(大小写敏感)
- 如果地址在收集器里存在但加载还是失败,检查Package是否初始化成功,清单是否加载
我遇到过一次地址找不到的问题,排查了半天发现是收集器保存后没有重新构建清单,代码里用的还是旧清单。所以修改收集器配置后,一定要重新构建清单。
5.2 资源重复打包:包体积异常
迁移后如果发现包体积比路径加载时大了很多,大概率是资源重复打包了。路径加载模式下,不同文件夹下的相同资源会各自打包。可寻址模式下,如果地址不同,YooAsset会认为是不同资源,也会各自打包。
解决办法是给相同资源分配相同的地址,或者把公共资源放到一个单独的收集器里,其他收集器通过依赖引用。YooAsset的收集器支持“共享资源”配置,你可以指定某个收集器为共享收集器,其他收集器会自动引用它的资源,不会重复打包。
我一般会建一个Shared收集器,把通用贴图、字体、Shader放进去,其他收集器在“共享收集器”字段里选择Shared,这样这些资源只会打包一次。
5.3 热更后资源加载异常:清单版本不匹配
热更后如果出现资源加载异常,比如加载出来的资源是旧版本,或者加载失败,通常是清单版本不匹配导致的。可寻址模式下,清单文件本身也有版本号,如果本地清单和远程清单版本不一致,会导致资源定位错误。
排查步骤:
- 检查远程清单文件的版本号是否和本地一致
- 检查热更流程是否正确更新了本地清单
- 检查Bundle的Hash值是否匹配,如果不匹配说明Bundle下载不完整
我遇到过一次热更后资源加载异常,排查发现是远程清单更新了但Bundle没更新,导致清单里的Hash值和实际Bundle不匹配。解决办法是在热更流程里增加Bundle完整性校验,下载完成后对比Hash值,不匹配就重新下载。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 地址找不到 | 收集器未配置该地址 | 在收集器面板搜索地址 | 添加资源到收集器或修正地址 |
| 加载失败 | Package未初始化 | 检查初始化返回值 | 确保InitializeAsync成功 |
| 包体积异常 | 资源重复打包 | 对比打包前后的资源列表 | 配置共享收集器 |
| 热更后异常 | 清单版本不匹配 | 对比本地和远程清单版本 | 重新下载清单和Bundle |
| 内存泄漏 | 资源未释放 | 检查Handle是否Release | 在适当时机调用Release |
| 加载速度慢 | 分组粒度过细 | 检查Bundle数量和体积 | 调整分组策略,合并小Bundle |
| 同步加载失败 | 远程资源不支持同步 | 检查PlayMode | 远程资源改用异步加载 |
5.5 独家避坑技巧
技巧一:地址常量类统一管理。不要直接在代码里写地址字符串,建一个静态类把所有地址定义为常量。这样改地址时只需要改一处,而且编译器会检查拼写错误。
public static class AssetAddress { public const string UI_MainPanel = "UI_MainPanel"; public const string UI_SettingsPanel = "UI_SettingsPanel"; public const string Char_Player = "Char_Player"; }技巧二:收集器配置版本化。收集器配置是存在YooAsset的配置文件里的,这个文件应该纳入版本管理(比如Git)。这样团队成员之间的配置保持一致,不会出现“我这边能加载你那边加载不了”的情况。
技巧三:打包前先预览。YooAsset的构建面板有“预览”功能,可以查看每个分组的资源列表和体积。打包前花两分钟预览一下,能避免很多低级错误,比如把不该打包的资源打进去了,或者分组体积过大。
技巧四:热更测试用真机。编辑器模式下的模拟构建和真机打包的行为有差异,尤其是资源路径和加载方式。热更流程一定要在真机上测试,模拟器或者编辑器模式下的测试结果不可靠。
技巧五:保留回滚方案。迁移过程中如果遇到无法解决的问题,要能快速回滚到路径加载模式。我的做法是在代码里保留两套加载逻辑,通过宏定义切换。迁移完成并稳定运行一段时间后,再删除路径加载的代码。
6. 迁移后的性能优化和扩展思路
6.1 加载性能的监控和调优
迁移完成后,加载性能是重点关注的指标。可寻址模式下,加载性能主要受三个因素影响:Bundle数量、Bundle体积、加载方式。
Bundle数量越多,加载时的IO次数越多,尤其是机械硬盘上,随机IO的性能很差。我一般会把Bundle数量控制在50个以内,单个Bundle体积控制在2-5MB。如果Bundle数量超过50个,考虑合并一些小的Bundle。
加载方式上,异步加载比同步加载更平滑,不会卡主线程。但异步加载的代码复杂度更高,需要处理回调、协程、取消等逻辑。我一般对UI资源用异步加载,对配置表等小资源用同步加载。
监控加载性能可以用YooAsset提供的统计接口,获取加载耗时、Bundle数量、内存占用等数据。我一般会在加载管理器里加日志,记录每次加载的耗时,超过阈值的加载会打警告,方便定位性能瓶颈。
6.2 内存管理的注意事项
可寻址模式下的内存管理比路径加载更严格,因为资源的引用计数是自动管理的。如果忘记释放资源,内存会持续增长,最终导致崩溃。
释放资源的时机很关键。UI资源一般在界面关闭时释放,角色资源在角色销毁时释放,场景资源在场景切换时释放。我一般会在资源加载管理器里维护一个引用计数,每次加载增加计数,每次释放减少计数,计数为零时才真正释放。
注意:释放资源时不要直接调用
Resources.UnloadUnusedAssets,这个API会扫描所有资源,耗时很长。YooAsset的Handle.Release会自动处理引用计数,不需要手动调用Unload。
6.3 后续扩展:多Package和远程加载
可寻址模式的一个优势是支持多Package。你可以把不同模块的资源放在不同的Package里,比如基础包、活动包、DLC包。每个Package独立打包、独立热更,互不影响。
多Package的配置在YooAsset面板里完成,创建多个Package,每个Package配置独立的收集器和打包参数。加载时通过YooAssets.GetPackage("PackageName")获取对应的Package,然后调用加载接口。
远程加载是可寻址模式的另一个优势。你可以把资源放在远程服务器上,游戏启动时从远程下载。YooAsset支持CDN加载,配置好远程地址后,加载远程资源就像加载本地资源一样简单。
远程加载的配置在HostPlayModeParameters里完成,指定RemoteServices的实现类,YooAsset会自动处理远程请求、缓存、重试等逻辑。我一般会用YooAsset内置的RemoteServices实现,如果需要自定义(比如加签名验证),可以继承IRemoteServices接口自己实现。
6.4 从路径加载迁移到可寻址模式的检查清单
最后整理一份迁移检查清单,你可以对照着逐项确认:
- [ ] YooAsset版本升级到1.5.x或更高
- [ ] 创建资源收集器,配置收集目录和地址规则
- [ ] 检查地址冲突,确保所有地址唯一
- [ ] 配置分组规则,控制单组体积在2-5MB
- [ ] 配置共享收集器,避免公共资源重复打包
- [ ] 替换所有路径加载代码为地址加载
- [ ] 封装资源加载管理器,统一管理加载和释放
- [ ] 调整打包流程,构建收集器清单后再打包
- [ ] 调整热更流程,对比清单版本和Bundle Hash
- [ ] 真机测试热更全流程
- [ ] 监控加载性能和内存占用
- [ ] 保留回滚方案,确保迁移失败可恢复
这份清单是我实际迁移过程中总结的,每一条都对应一个具体的操作步骤。你按这个清单走一遍,基本能覆盖迁移的主要环节。迁移完成后,建议再跑一周的稳定性测试,观察内存和加载耗时是否有异常,确认没问题后再删除路径加载的旧代码。
我在实际迁移中最大的体会是:可寻址模式的价值不在于加载代码变短了,而在于资源管理的逻辑变清晰了。路径加载时,资源的位置和引用是耦合的,改一处动全身。可寻址模式下,位置和引用解耦,收集器负责位置,代码负责引用,各司其职。这种解耦带来的可维护性提升,在项目后期会越来越明显。