24.总结
24.1 知识点

学习的主要内容

总结讲什么

对比

选择建议

总结

24.2 核心要点速览
Addressables 概述
Addressables 是什么:Unity 官方的资源管理系统,本质是 AssetBundle 的上层封装。它把资源加载、打包、更新、内存管理等复杂操作封装成一套统一 API,开发者只需通过”地址”或”标签”就能加载资源,无需手动管理 AssetBundle 的创建、加载、卸载。
核心优势:
- 统一 API:无论资源在本地还是远程,加载方式一致
- 自动管理:引用计数、内存释放、依赖追踪都由系统处理
- 热更新友好:支持远程资源更新,无需重新打包整包
学习前提建议:
- AssetBundle 基础概念(什么是 AB、依赖关系)
- Resources 加载方式及其缺陷
- 异步加载机制(协程、async/await)
系列内容结构:
| 模块 | 内容 |
|---|---|
| 资源加载基础 | 寻址设置、指定资源加载、Label 标签、动态加载单个/多个资源 |
| 配置相关 | Profile 窗口、AddressableAssetSettings、PackedAssets、Hosting 服务 |
| 资源打包加载 | 打包理论、打包加载流程、资源更新机制 |
| 资源加载进阶 | 资源定位加载、异步加载方式、AsyncOperationHandle、自定义更新、引用计数 |
| 窗口与问题 | 事件查看、分析窗口、构建布局报告、常见问题 |
导入与初始化
安装 Addressables 包:
- 打开
Window -> Package Manager - 切换包源为
Unity Registry - 搜索
Addressables并安装 - 验证:Project 窗口右键菜单出现
Addressables选项
创建配置文件(两种方式):
| 方式 | 操作 | 生成内容 |
|---|---|---|
| Groups 窗口 | Window -> Asset Management -> Addressables -> Groups → 点击 Create Addressables Settings |
Assets/AddressableAssetsData/ 文件夹及配置文件 |
| Inspector 勾选 | 选中资源 → Inspector 勾选 Addressable | 自动创建配置文件(若不存在) |
注意:首次使用建议通过 Groups 窗口创建,便于后续管理。
可寻址资源设置
让资源变为可寻址:
| 方式 | 操作 |
|---|---|
| Inspector 勾选 | 选中资源 → Inspector 勾选 Addressable |
| 拖入 Groups 窗口 | 选中资源 → 拖入 Addressables Groups 窗口 |
注意事项:
- C# 代码无法作为可寻址资源
- Resources 文件夹下资源变为可寻址后,会自动移入
Resources_moved文件夹(避免重复打包) - 普通文件夹下资源变为可寻址后不会移动
Groups 窗口核心信息:
| 字段 | 说明 |
|---|---|
| Group Name | 分组名,一个组可作为一个或多个 AB 包 |
| Addressable Name | 可寻址名(可重名),用于加载资源 |
| Path | 资源路径(不可重复,用于定位) |
| Labels | 标签(可重复),用于分类和筛选 |
Play Mode Script(编辑器运行模式):
| 模式 | 说明 | 适用阶段 |
|---|---|---|
| Use Asset Database | 直接从资源数据库加载,无需打包 | 开发阶段 |
| Simulate Groups | 模拟 AB 包加载,可设置延迟模拟下载速度 | 测试阶段 |
| Use Existing Build | 从已构建的 AB 包加载 | 发布测试阶段 |
资源名规则:
- 资源路径不允许相同(后缀不同、名字相同可以)
- 资源名可随意修改
- 加载时用「名字 + 标签」双标识定位资源
AssetReference 资源标识类
常用标识类:
| 类名 | 用途 |
|---|---|
AssetReference |
通用标识,可加载任意类型 |
AssetReferenceGameObject |
GameObject 预设体 |
AssetReferenceSprite |
精灵图片 |
AssetReferenceTexture |
贴图 |
AssetReferenceAtlasedSprite |
图集 |
AssetReferenceT<T> |
指定类型标识 |
加载与释放:
// 加载资源
assetReference.LoadAssetAsync<GameObject>().Completed += (handle) => {
if (handle.Status == AsyncOperationStatus.Succeeded)
Instantiate(handle.Result);
};
// 释放资源(加载成功且使用完毕后调用)
assetReference.ReleaseAsset();
// 直接实例化(仅适用于 GameObject)
assetReferenceGameObject.InstantiateAsync();
释放注意:
- 释放后
AssetReference.Asset置空,但AsyncOperationHandle.Result不为空 - 实例化的 GameObject 不受释放影响(用的是预制体配置)
- 直接引用的资源(如材质赋值)在 AB 包模式下释放后会丢失
Label 标签
作用:让相同功能的不同资源使用相同名字,通过标签区分。
典型场景:
- 装备品质:同名资源 + Red/Green/Blue 标签
- 画质档位:同名资源 + HD/SD/FHD 标签
- 节日换肤:同名资源 + MidAutumn/Spring/Christmas 标签
约束标识类关联标签:
[AssetReferenceUILabelRestriction("SD", "HD", "FHD")]
public AssetReference assetReference;
动态加载单个资源
通过名字或标签加载:
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");
handle.Completed += (h) => {
if (h.Status == AsyncOperationStatus.Succeeded)
Instantiate(h.Result);
Addressables.Release(h); // 使用完毕后释放
};
加载场景:
Addressables.LoadSceneAsync("SceneName", LoadSceneMode.Single, false).Completed += (handle) => {
handle.Result.ActivateAsync().completed += (a) => {
Debug.Log("场景激活完成");
Addressables.Release(handle);
};
};
注意:
- 同名同类型资源会加载第一个匹配项
- 同名不同类型资源可通过泛型区分
- 释放需传入对应的
AsyncOperationHandle
动态加载多个资源
加载所有匹配资源:
AsyncOperationHandle<IList<Object>> handle = Addressables.LoadAssetsAsync<Object>("Cube", (obj) => {
// 每个资源回调一次
print(obj.name);
});
handle.Completed += (h) => {
foreach (var item in h.Result)
print(item.name);
Addressables.Release(h);
};
多条件合并加载:
| MergeMode | 说明 | 示例结果 |
|---|---|---|
| None / UseFirst | 使用第一组结果 | [1,2,3] |
| Union | 合并所有结果 | [1,2,3,4] |
| Intersection | 取交集 | [1,3] |
List<string> keys = new List<string> { "Cube", "Red" };
Addressables.LoadAssetsAsync<Object>(keys, (obj) => {
print(obj.name);
}, Addressables.MergeMode.Intersection);
Profile 概述窗口
作用:配置 Addressables 打包和加载时的路径变量。
核心变量:
| 变量 | 说明 |
|---|---|
| BuildTarget | 构建目标平台 |
| LocalBuildPath | 本地构建路径(默认在 Library 中) |
| LocalLoadPath | 本地加载路径 |
| RemoteBuildPath | 远程构建路径 |
| RemoteLoadPath | 远程加载路径(下载 URL) |
变量语法:
[变量名]:打包时计算,如[BuildTarget]{变量名}:运行时计算,如{Addressables.RuntimePath}- 方括号和大括号内必须是静态变量或属性
多平台配置:针对不同平台远程分发内容时,建议创建多个 Profile 配置文件。
AddressableAssetSettings 配置
主要设置项:
| 分类 | 关键配置 |
|---|---|
| Profile | 选择使用哪套配置文件 |
| Diagnostics | Send Profiler Events(启用分析器)、Log Runtime Exceptions |
| Catalog | Player Version Override(版本号)、Compress Local Catalog(压缩目录) |
| Content Update | Build Remote Catalog(构建远程目录)、Disable Catalog Update on Startup |
| Downloads | Max Concurrent Web Requests、Catalog Download Timeout |
| Build | Ignore Invalid Files、Unique Bundle IDs、Contiguous Bundles |
Catalog 目录:将资源地址映射到物理位置。压缩目录可减小体积但增加加载时间。
Content Update:
Can Change Post Release:资源改变后重建整个包Cannot Change Post Release:改变的资源移到新组,用于增量更新
PackedAssets 组配置
Content Packing & Loading:
| 配置 | 说明 |
|---|---|
| Build Path | 打包路径(Local/Remote) |
| Load Path | 加载路径(Local/Remote) |
AB 包压缩方式:
| 方式 | 特点 |
|---|---|
| LZ4 | 推荐,按需解压,内存占用低 |
| LZMA | 压缩率最高,但解压慢,加载需解压整个包 |
| Uncompressed | 不压缩,包体大 |
Bundle Mode 打包模式:
| 模式 | 说明 |
|---|---|
| Pack Together | 组内所有资源打成一个包 |
| Pack Separately | 每个资源单独打一个包 |
| Pack Together by Label | 相同标签的资源打成一个包 |
Content Update Restriction:
Can Change Post Release:发行后可修改,改动后重建整个包Cannot Change Post Release:发行后不可修改,改动资源移到更新组
Hosting 托管服务
作用:在本地模拟 HTTP 服务器,用于测试远程加载。
使用方式:
- 打开
Window -> Asset Management -> Addressables -> Hosting - 点击
Create -> Local Hosting - 启用后自动分配端口
Profile 变量配置:
http://[PrivateIpAddress]:[HostingServicePort]/[BuildTarget]
注意:Unity 自带托管有时会失效,可用第三方工具如 HFS 搭建 HTTP 服务器。
资源打包理论
打包本质:将可寻址资源打包到 AssetBundle 中,以组为单位进行打包。
打包用途:
- 单机游戏:减小包体,所有 AB 包随游戏发布
- 网络游戏:热更新,只打包必备资源,其余放远端服务器
打包注意事项:
| 问题 | 说明 | 解决方案 |
|---|---|---|
| 场景资源 | 场景始终单独打包 | 无需处理,系统自动分离 |
| 资源依赖 | 非可寻址资源被多个组引用会重复打包 | 将共享资源也设为可寻址 |
| 图集问题 | 不同包使用同一图集会导致重复 | 图集单独作为可寻址资源 |
分组建议:同类型、同作用的资源放一起,如角色组、怪物组、UI 组、图集组等。
本地资源发布
流程:
- 设置组的 Build Path 为 LocalBuildPath
- 设置组的 Load Path 为 LocalLoadPath
- 点击
Build -> New Build -> Default Build Script
输出位置:
- 默认打包到
Library/com.unity.addressables/aa/Windows/ - 发布应用时自动复制到
StreamingAssets
注意事项:
- 不建议修改默认的本地构建和加载路径
- 修改后需要手动将 AB 包复制到 StreamingAssets
远程资源发布
模拟远端流程:
- 打开 Hosting 窗口,创建 Local Hosting 并启用
- 修改 Profile 的 RemoteLoadPath 为
http://[PrivateIpAddress]:[HostingServicePort]/[BuildTarget] - 设置组为 RemoteBuildPath 和 RemoteLoadPath
- 勾选 AddressableAssetSettings 的 Build Remote Catalog
- 打包后 AB 包生成在 ServerData 文件夹
实际远端流程:
- 使用 HFS 等工具搭建 HTTP 服务器
- 修改 RemoteLoadPath 为实际服务器 URL
- 打包后将 ServerData 内容上传到服务器
资源更新
Content Update Restriction 更新限制:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| Can Change Post Release | 整包更新,资源变化后重建整个包 | 大范围资源更新 |
| Cannot Change Post Release | 局部更新,变化资源移到更新组 | 小范围增量更新 |
整包更新流程:
- 组设置为 Can Change Post Release
- 修改资源后点击
Update a Previous Build - 选择
addressables_content_state.bin文件 - 上传新生成的 AB 包到服务器
局部更新流程:
- 组设置为 Cannot Change Post Release
- 修改资源后点击
Tools -> Check for Content Update Restrictions - 选择变化的资源,应用到更新组
- 点击
Update a Previous Build - 上传 AB 包到服务器
关键点:更新是通过 Update a Previous Build 而非重新打包,否则需要重新发布应用程序。
资源定位信息加载
作用:先获取资源定位信息,再根据定位信息加载资源。可用于处理多类型资源加载。
获取定位信息:
AsyncOperationHandle<IList<IResourceLocation>> handle =
Addressables.LoadResourceLocationsAsync("Cube", typeof(GameObject));
handle.Completed += (h) => {
foreach (var loc in h.Result)
{
print(loc.PrimaryKey); // 资源名
print(loc.InternalId); // 资源路径
print(loc.ResourceType); // 资源类型
Addressables.LoadAssetAsync<GameObject>(loc).Completed += (obj) => {
Instantiate(obj.Result);
};
}
};
定位信息属性:
PrimaryKey:资源主键(资源名)InternalId:资源内部 ID(资源路径)ResourceType:资源类型
异步加载方式
三种方式对比:
| 方式 | 特点 | 适用场景 |
|---|---|---|
| 事件监听 | Completed 回调 | 简单加载 |
| 协程 | yield return handle | 需要顺序执行 |
| async/await | await handle.Task | 多任务并行 |
协程方式:
IEnumerator LoadCoroutine()
{
var handle = Addressables.LoadAssetAsync<GameObject>("Cube");
if (!handle.IsDone)
yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded)
Instantiate(handle.Result);
}
async/await 方式:
async void LoadAsync()
{
var handle = Addressables.LoadAssetAsync<GameObject>("Cube");
await handle.Task;
Instantiate(handle.Result);
}
// 多任务并行
async void LoadMultiAsync()
{
var h1 = Addressables.LoadAssetAsync<GameObject>("Cube");
var h2 = Addressables.LoadAssetAsync<GameObject>("Sphere");
await Task.WhenAll(h1.Task, h2.Task);
Instantiate(h1.Result);
Instantiate(h2.Result);
}
注意:WebGL 平台不支持 async/await 语法。
AsyncOperationHandle 进阶
获取加载进度:
while (!handle.IsDone)
{
DownloadStatus status = handle.GetDownloadStatus();
print(status.Percent); // 进度 0~1
print(status.DownloadedBytes + "/" + status.TotalBytes); // 字节数
yield return null;
}
句柄类型转换:
// 有类型 → 无类型
AsyncOperationHandle temp = typedHandle;
// 无类型 → 有类型
AsyncOperationHandle<GameObject> typed = temp.Convert<GameObject>();
强制同步加载:
handle.WaitForCompletion(); // 阻塞主线程等待加载完成
注意:WaitForCompletion 会阻塞主线程,Unity 2020.1 及之前版本会等待所有未完成的异步操作,不建议常规使用。
目录更新与预加载
目录文件:
- JSON 文件:记录 AB 包路径、资源类型、加载脚本等
- Hash 文件:用于判断目录是否变化
手动更新目录:
// 方式一:自动检查并更新
Addressables.UpdateCatalogs().Completed += (h) => { Addressables.Release(h); };
// 方式二:先检查再更新
Addressables.CheckForCatalogUpdates().Completed += (h) => {
if (h.Result.Count > 0)
Addressables.UpdateCatalogs(h.Result);
};
预加载 AB 包:
IEnumerator Preload()
{
// 获取下载大小
var sizeHandle = Addressables.GetDownloadSizeAsync("Cube");
yield return sizeHandle;
if (sizeHandle.Result > 0)
{
// 下载依赖
var downloadHandle = Addressables.DownloadDependenciesAsync("Cube");
while (!downloadHandle.IsDone)
{
print(downloadHandle.GetDownloadStatus().Percent);
yield return null;
}
Addressables.Release(downloadHandle);
}
}
使用时机:进入游戏或切换场景时预加载,避免运行时卡顿。
引用计数规则
机制:
- 加载资源:引用计数 +1
- 释放资源:引用计数 -1
- 引用计数为 0:自动卸载资源
AB 包引用计数:
- 从 AB 包加载资源:AB 包引用 +1
- 从 AB 包卸载资源:AB 包引用 -1
- AB 包引用为 0:卸载整个 AB 包
注意事项:
- 释放资源不一定立即从内存卸载(AB 包可能还有其他引用)
- 建议在场景切换时调用
Resources.UnloadUnusedAssets - 加载和释放必须配对使用,避免内存泄漏
资源管理器优化:需要自己维护引用计数,避免复用 handle 导致计数失效。
public class AddressablesInfo
{
public AsyncOperationHandle handle;
public uint count; // 引用计数
}
调试工具
Event Viewer 事件查看窗口:
- 用途:监视资源内存管理,排查内存泄漏
- 前提:勾选 AddressableAssetSettings 的 SendProfilerEvents
- 打开:Window > Asset Management > Addressables > Event Viewer
- 功能:显示加载/卸载事件、引用计数、帧率、内存使用
Analyze 分析窗口:
- 用途:分析 AB 包布局,检查潜在问题
- 打开:Window > Asset Management > Addressables > Analyze
常用规则:
| 规则 | 功能 | 是否可修复 |
|---|---|---|
| Check Duplicate Bundle Dependencies | 检查重复依赖 | ✓ 可修复 |
| Check Resources to Addressable Duplicate Dependencies | 检查 Resources 与 AB 重复 | ✗ 仅分析 |
| Check Scene to Addressable Duplicate Dependencies | 检查场景与 AB 重复 | ✗ 仅分析 |
| Bundle Layout Preview | 预览 AB 包布局 | ✗ 仅分析 |
Build Layout 构建布局报告:
- 用途:查看 AB 包详细信息(大小、依赖、资源列表)
- 启用:Edit > Preferences > Addressables > Debug Build Layout
- 位置:
Library/com.unity.addressables/buildlayout.txt
打包策略选择
多包策略(包小而多):
- 优点:灵活加载卸载,内存占用可控
- 缺点:目录文件大,并发下载慢,共享资源可能重复
大包策略(包大而少):
- 优点:目录文件小,下载效率高
- 缺点:下载失败需重下,无法单独卸载
建议:根据资源使用情况合理分组,同类型资源放一起。
压缩方式选择
| 压缩方式 | 特点 | 适用场景 |
|---|---|---|
| 无压缩 | 加载最快,包体最大 | 小型单机游戏 |
| LZ4 | 基于块压缩,可部分加载 | 本地资源(推荐) |
| LZMA | 压缩率最高,加载最慢 | 远端资源 |
注意:压缩只影响包体大小和下载时间,不影响运行时内存。
优化建议
减小目录文件:
- 启用 Compress Local Catalog 压缩本地目录
- 禁用 BuiltInData 组的 IncludeResources 和 IncludeBuildSettings
注意事项:
- AB 包大小建议控制在 4GB 以下(兼容性)
- Groups View 中可禁用 Show Sprite and Subobject Addresses 提升性能
- 使用带破折号的组名可自动折叠分组
24.3 面试题精选
基础题
1. Addressables 的三种 Play Mode Script 有什么区别?
题目
Addressables 的 Groups 窗口中 Play Mode Script 有三种模式,请说明它们的区别及适用场景。
深入解析
Play Mode Script 决定了在 Unity 编辑器中运行游戏时,Addressables 如何加载资源。
| 模式 | 加载方式 | 特点 | 适用场景 |
|---|---|---|---|
| Use Asset Database | 直接从 Assets 文件夹加载 | 最快,无需打包 | 开发阶段快速迭代 |
| Simulate Groups | 模拟 AB 包加载流程 | 可模拟下载延迟、分析依赖 | 测试阶段验证加载逻辑 |
| Use Existing Build | 从已构建的 AB 包加载 | 真实环境,需先 Build | 发布前最终验证 |
Use Asset Database 模式下,资源直接从项目文件夹读取,跳过了 AssetBundle 的打包和加载流程,适合开发时快速调试。Simulate Groups 会模拟真实的 AB 包加载流程,包括依赖解析和下载延迟,但不实际生成 AB 包文件。Use Existing Build 则是完全真实的环境,必须先执行 Build 操作生成 AB 包。
答题示例
三种模式分别是 Use Asset Database、Simulate Groups 和 Use Existing Build。
Use Asset Database 直接从资源数据库加载,速度最快,适合开发阶段快速迭代,但无法测试真实的打包加载流程。
Simulate Groups 模拟 AB 包加载,可以设置延迟模拟下载速度,适合测试阶段验证资源加载逻辑和依赖关系。
Use Existing Build 从已构建的 AB 包加载,是真实环境,适合发布前的最终验证,但需要先执行 Build 操作。
参考文章
- 3.资源加载基础-寻址资源设置
2. AssetReference 和 Addressables.LoadAssetAsync 有什么区别?
题目
Addressables 提供了两种资源加载方式:通过 AssetReference 标识类加载,以及通过 Addressables.LoadAssetAsync 动态加载。请说明它们的区别和适用场景。
深入解析
| 对比项 | AssetReference | Addressables.LoadAssetAsync |
|---|---|---|
| 绑定方式 | Inspector 面板拖拽关联 | 代码中传入资源名或标签 |
| 灵活性 | 固定关联,编译时确定 | 动态传入,运行时确定 |
| 类型安全 | 有类型特化版本 | 通过泛型指定 |
| 适用场景 | 小项目、固定资源 | 商业项目、配置驱动加载 |
AssetReference 适合资源在开发时就已确定的情况,通过 Inspector 关联后不会出错。但在商业项目中,资源加载往往由配置表驱动,需要根据配置动态决定加载哪个资源,这时 Addressables.LoadAssetAsync 更合适。
释放资源时,AssetReference 调用 ReleaseAsset(),而动态加载需要保存 AsyncOperationHandle 并调用 Addressables.Release(handle)。
答题示例
AssetReference 通过 Inspector 面板关联资源,适合资源在开发时就已确定的场景,比如小项目或固定 UI 元素。
Addressables.LoadAssetAsync 通过资源名或标签动态加载,适合商业项目中配置驱动的资源加载,灵活性更高。
释放方式也不同:AssetReference 调用 ReleaseAsset 方法,动态加载需要保存 AsyncOperationHandle 并调用 Addressables.Release。
参考文章
- 4.资源加载基础-指定资源加载
- 6.资源加载基础-动态加载单个资源
进阶题
1. Addressables 释放资源后,已实例化的对象会怎样?
题目
调用 Addressables.Release 或 AssetReference.ReleaseAsset 释放资源后,已经通过该资源实例化的 GameObject 会怎样?直接引用的资源(如材质)会怎样?
深入解析
释放资源后的影响取决于资源的使用方式:
实例化的 GameObject:不受影响。因为实例化时使用的是预制体的配置数据,生成的是独立对象,与原资源已无直接引用关系。
直接引用的资源:在 AB 包模式下会丢失。比如将加载的材质直接赋值给 MeshRenderer,释放材质资源后,该材质引用会变成 null 或丢失。这是因为在 AB 包模式下,资源是从 AssetBundle 加载的,释放后 AssetBundle 卸载,资源引用失效。
开发模式下的陷阱:Use Asset Database 和 Simulate Groups 模式下可能看不出问题,因为资源实际还在内存中。只有 Use Existing Build 模式才会暴露这个 bug。
// 错误示例:释放后材质丢失
assetReferenceTMaterial.LoadAssetAsync().Completed += (handle) => {
cube.GetComponent<MeshRenderer>().material = handle.Result;
assetReferenceTMaterial.ReleaseAsset(); // AB包模式下材质丢失
};
// 正确做法:使用完毕后再释放
答题示例
实例化的 GameObject 不受影响,因为实例化生成的是独立对象,使用的是预制体的配置数据。
但直接引用的资源在 AB 包模式下会丢失。比如把加载的材质赋值给 MeshRenderer,释放材质后引用会失效。
开发时要注意:Use Asset Database 模式下可能看不出问题,只有 Use Existing Build 才会暴露。所以释放资源的时机很重要,必须确保不再使用该资源。
参考文章
- 4.资源加载基础-指定资源加载
- 6.资源加载基础-动态加载单个资源
2. 如何设计一个 Addressables 资源管理器?
题目
在商业项目中,直接在各脚本中管理 AsyncOperationHandle 会很混乱。请设计一个 Addressables 资源管理器,支持单个资源加载、多资源加载、资源释放等功能。
深入解析
设计思路:
核心问题:AsyncOperationHandle 需要保存以便后续释放,分散管理容易遗漏。
解决方案:用字典统一管理所有加载操作,key 由「资源名 + 类型名」组成,value 为 AsyncOperationHandle。
关键设计点:
重复加载处理:同一资源重复加载时,检查字典中是否已存在。如果已完成直接回调,未完成则添加到 Completed 事件。
类型区分:同名不同类型资源需要区分,key 中加入类型名。
多资源加载:支持 MergeMode,key 需要包含所有条件。
public class AddressablesManager : MonoBehaviour
{
private Dictionary<string, IEnumerator> handleDict = new Dictionary<string, IEnumerator>();
public void LoadAssetAsync<T>(string name, Action<AsyncOperationHandle<T>> callback)
{
string key = name + "_" + typeof(T).Name;
if (handleDict.TryGetValue(key, out var value))
{
var handle = (AsyncOperationHandle<T>)value;
if (handle.IsDone)
callback(handle);
else
handle.Completed += callback;
return;
}
var newHandle = Addressables.LoadAssetAsync<T>(name);
newHandle.Completed += (h) => {
if (h.Status == AsyncOperationStatus.Succeeded)
callback(h);
else
handleDict.Remove(key);
};
handleDict.Add(key, newHandle);
}
public void Release<T>(string name)
{
string key = name + "_" + typeof(T).Name;
if (handleDict.TryGetValue(key, out var value))
{
Addressables.Release((AsyncOperationHandle<T>)value);
handleDict.Remove(key);
}
}
}
答题示例
核心是用字典统一管理 AsyncOperationHandle,key 由资源名加类型名组成,避免同名不同类型资源冲突。
重复加载时检查字典:已完成则直接回调,未完成则添加到 Completed 事件,避免重复发起加载请求。
多资源加载需要支持 MergeMode 参数,key 要包含所有条件和合并模式。
释放时从字典取出 handle 调用 Addressables.Release,并从字典移除。建议提供 Clear 方法在场景切换时统一清理。
参考文章
- 6.资源加载基础-动态加载单个资源
- 7.资源加载基础-动态加载多个资源
3. Addressables 的 Bundle Mode 有哪几种?如何选择?
题目
PackedAssets 配置中的 Bundle Mode 有三种模式,请说明它们的区别及适用场景。
深入解析
| 模式 | 打包方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Pack Together | 组内所有资源打成一个包 | 减少包数量,依赖共享 | 单个包可能过大,改动需重建整个包 | 功能模块、UI 包 |
| Pack Separately | 每个资源单独打包 | 精确控制,改动影响小 | 包数量多,依赖重复 | 大型独立资源 |
| Pack Together by Label | 相同标签资源打成一个包 | 按功能分类,灵活 | 需要合理规划标签 | 按品质/平台分包 |
选择依据:
- 资源大小:小资源适合 Pack Together,大资源适合 Pack Separately
- 更新频率:频繁更新的资源适合 Pack Separately
- 依赖关系:有共同依赖的资源适合打在一起
- 加载时机:同时加载的资源适合打在一起
答题示例
三种模式分别是 Pack Together、Pack Separately 和 Pack Together by Label。
Pack Together 把组内所有资源打成一个包,适合功能模块或 UI 包,减少包数量但改动需重建整个包。
Pack Separately 每个资源单独打包,适合大型独立资源或频繁更新的资源,精确控制但包数量多。
Pack Together by Label 按标签分组打包,适合按品质或平台分包的场景。
选择时要考虑资源大小、更新频率、依赖关系和加载时机。
参考文章
- 10.配置相关-PackedAssets打包资源数据配置
4. Addressables 整包更新和局部更新有什么区别?
题目
Addressables 的 Content Update Restriction 有两种模式,请说明整包更新和局部更新的区别及适用场景。
深入解析
| 对比项 | 整包更新 | 局部更新 |
|---|---|---|
| 设置 | Can Change Post Release | Cannot Change Post Release |
| 更新方式 | 资源变化后重建整个包 | 变化资源移到新组单独打包 |
| 下载量 | 较大,需下载整个包 | 较小,只下载变化部分 |
| 适用场景 | 大范围资源更新 | 小范围增量更新 |
| 操作步骤 | 直接 Update a Previous Build | 先 Check for Content Update,再 Update |
整包更新:当组内任意资源变化,整个组的 AB 包都会重新构建。玩家需要重新下载整个包,适合大版本更新或资源大规模改动。
局部更新:变化的资源会被移到自动创建的更新组,单独打成一个新包。Addressables 会自动加载最新版本,玩家只需下载变化的部分。
关键操作:两种更新都使用 Update a Previous Build,需要选择 addressables_content_state.bin 文件进行对比。不是重新打包,否则需要重新发布应用程序。
答题示例
整包更新对应 Can Change Post Release,资源变化后重建整个包,适合大范围更新,但玩家下载量大。
局部更新对应 Cannot Change Post Release,变化资源移到更新组单独打包,适合小范围增量更新,玩家只下载变化部分。
两种更新都通过 Update a Previous Build 操作,选择 addressables_content_state.bin 文件对比。关键是不重新打包,否则需要重新发布应用程序。
参考文章
- 14.资源打包加载-资源更新
深度题
1. Addressables 的异步加载有哪几种方式?
题目
Addressables 支持多种异步加载方式,请说明它们的区别及适用场景。
深入解析
Addressables 提供三种异步加载方式:
| 方式 | 特点 | 适用场景 |
|---|---|---|
| 事件监听 | Completed 回调 | 简单加载 |
| 协程 | yield return handle | 需要顺序执行 |
| async/await | await handle.Task | 多任务并行 |
事件监听方式:通过 Completed 事件回调获取加载结果,适合简单的单次加载场景。缺点是回调嵌套时代码可读性差。
协程方式:使用 yield return 等待加载完成,适合需要顺序执行多个加载任务的场景。可以在协程中获取加载进度,实现 Loading 界面。
async/await 方式:使用 Task 并行处理多个加载任务,代码更简洁。Task.WhenAll 可以同时等待多个加载完成,效率最高。
平台限制:
- WebGL 平台不支持 async/await 语法,必须使用协程或事件监听
- 移动平台三种方式都支持,推荐 async/await 或协程
性能考量:
- 单资源加载:三种方式性能差异不大
- 多资源并行:async/await + Task.WhenAll 效率最高
- 需要进度显示:协程最方便,可在循环中获取 DownloadStatus
答题示例
Addressables 提供三种异步加载方式:事件监听、协程和 async/await。
事件监听通过 Completed 回调获取结果,适合简单加载场景,但回调嵌套时代码可读性差。
协程使用 yield return 等待加载完成,适合需要顺序执行或显示加载进度的场景。
async/await 可以通过 Task.WhenAll 并行处理多个任务,代码简洁效率高,但 WebGL 平台不支持。
实际项目中,移动端推荐 async/await 处理并行加载,WebGL 端必须用协程或事件监听。
参考文章
- 16.其他_资源加载相关_异步加载的几种使用方式
2. Addressables 的引用计数机制是怎样的?
题目
请说明 Addressables 的引用计数机制,以及在实际开发中需要注意什么。
深入解析
Addressables 内部维护引用计数来管理资源内存:
资源引用计数:
- 加载资源时:引用计数 +1
- 释放资源时:引用计数 -1
- 引用计数为 0 时:自动卸载资源
AB 包引用计数:
- 从 AB 包加载资源:AB 包引用 +1
- 从 AB 包卸载资源:AB 包引用 -1
- AB 包引用为 0 时:卸载整个 AB 包
注意事项:
- 释放资源不一定立即从内存卸载,因为 AB 包可能还有其他资源在使用
- 加载和释放必须配对使用,否则会导致内存泄漏或资源丢失
- 建议在场景切换时调用
Resources.UnloadUnusedAssets清理残留资源 - 自定义资源管理器时需要自己维护引用计数,避免复用 handle 导致计数失效
示例:一个 AB 包有资源 a 和 b,加载 a 两次、b 一次。释放 a 两次后,由于 b 还在使用,AB 包不会卸载,a 的内存也不会释放。需要调用 Resources.UnloadUnusedAssets 或等 b 也释放后才会卸载。
答题示例
Addressables 通过引用计数管理资源内存。加载资源时计数加一,释放时减一,计数为零时自动卸载。
AB 包也有自己的引用计数,只有当包内所有资源都释放后,AB 包才会卸载。
实际开发中要注意:释放资源不一定立即释放内存,因为 AB 包可能还有其他引用;加载和释放必须配对使用;场景切换时建议调用 Resources.UnloadUnusedAssets 清理残留资源。
参考文章
- 19.其他_资源加载相关_引用计数规则
3. 如何排查 Addressables 的内存泄漏问题?
题目
项目中使用 Addressables 后出现内存持续增长,可能是什么原因?如何排查和解决?
深入解析
常见内存泄漏原因:
| 原因 | 说明 | 解决方案 |
|---|---|---|
| 加载未释放 | LoadAssetAsync 后未调用 Release | 确保加载和释放配对 |
| Handle 未保存 | 保存了资源引用但丢失了 handle | 用字典统一管理 handle |
| 引用计数错误 | 自定义管理器复用 handle 导致计数失效 | 自己维护引用计数 |
| 场景切换未清理 | 切换场景时未释放旧资源 | 提供 Clear 方法统一清理 |
排查工具:Event Viewer
- 启用分析:AddressableAssetSettings → Send Profiler Events
- 打开窗口:Window → Asset Management → Addressables → Event Viewer
- 观察内容:
- 加载事件(绿色)和释放事件(红色)是否配对
- 引用计数是否归零
- AB 包是否正确卸载
排查步骤:
- 运行游戏到内存增长的场景
- 打开 Event Viewer,观察当前帧的资源引用
- 执行本应释放资源的操作(如关闭 UI、切换场景)
- 检查 Event Viewer 中是否还有未释放的引用
- 定位到具体资源,检查代码中是否遗漏 Release 调用
预防措施:
- 封装资源管理器,统一管理 handle 的生命周期
- 场景切换时调用 Clear 方法强制清理
- 使用 using 语句或 try-finally 确保释放
答题示例
内存泄漏常见原因是加载后未释放、handle 丢失或引用计数错误。
排查时使用 Event Viewer 窗口,启用 Send Profiler Events 后观察加载和释放事件是否配对。
具体步骤:运行到问题场景 → 打开 Event Viewer → 执行释放操作 → 检查是否还有未释放引用 → 定位代码遗漏点。
预防措施包括:封装资源管理器统一管理 handle、场景切换时强制清理、确保加载和释放配对调用。
参考文章
- 19.其他_资源加载相关_引用计数规则
- 20.其他_窗口相关_事件查看窗口
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com