24.Addressables总结

  1. 24.总结
    1. 24.1 知识点
      1. 学习的主要内容
      2. 总结讲什么
      3. 对比
      4. 选择建议
      5. 总结
    2. 24.2 核心要点速览
      1. Addressables 概述
      2. 导入与初始化
      3. 可寻址资源设置
      4. AssetReference 资源标识类
      5. Label 标签
      6. 动态加载单个资源
      7. 动态加载多个资源
      8. Profile 概述窗口
      9. AddressableAssetSettings 配置
      10. PackedAssets 组配置
      11. Hosting 托管服务
      12. 资源打包理论
      13. 本地资源发布
      14. 远程资源发布
      15. 资源更新
      16. 资源定位信息加载
      17. 异步加载方式
      18. AsyncOperationHandle 进阶
      19. 目录更新与预加载
      20. 引用计数规则
      21. 调试工具
      22. 打包策略选择
      23. 压缩方式选择
      24. 优化建议
    3. 24.3 面试题精选
      1. 基础题
        1. 1. Addressables 的三种 Play Mode Script 有什么区别?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. AssetReference 和 Addressables.LoadAssetAsync 有什么区别?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      2. 进阶题
        1. 1. Addressables 释放资源后,已实例化的对象会怎样?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 如何设计一个 Addressables 资源管理器?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. Addressables 的 Bundle Mode 有哪几种?如何选择?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. Addressables 整包更新和局部更新有什么区别?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      3. 深度题
        1. 1. Addressables 的异步加载有哪几种方式?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. Addressables 的引用计数机制是怎样的?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. 如何排查 Addressables 的内存泄漏问题?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章

24.总结


24.1 知识点

学习的主要内容

总结讲什么

对比

选择建议

总结


24.2 核心要点速览

Addressables 概述

Addressables 是什么:Unity 官方的资源管理系统,本质是 AssetBundle 的上层封装。它把资源加载、打包、更新、内存管理等复杂操作封装成一套统一 API,开发者只需通过”地址”或”标签”就能加载资源,无需手动管理 AssetBundle 的创建、加载、卸载。

核心优势

  • 统一 API:无论资源在本地还是远程,加载方式一致
  • 自动管理:引用计数、内存释放、依赖追踪都由系统处理
  • 热更新友好:支持远程资源更新,无需重新打包整包

学习前提建议

  • AssetBundle 基础概念(什么是 AB、依赖关系)
  • Resources 加载方式及其缺陷
  • 异步加载机制(协程、async/await)

系列内容结构

模块 内容
资源加载基础 寻址设置、指定资源加载、Label 标签、动态加载单个/多个资源
配置相关 Profile 窗口、AddressableAssetSettings、PackedAssets、Hosting 服务
资源打包加载 打包理论、打包加载流程、资源更新机制
资源加载进阶 资源定位加载、异步加载方式、AsyncOperationHandle、自定义更新、引用计数
窗口与问题 事件查看、分析窗口、构建布局报告、常见问题

导入与初始化

安装 Addressables 包

  1. 打开 Window -> Package Manager
  2. 切换包源为 Unity Registry
  3. 搜索 Addressables 并安装
  4. 验证: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 服务器,用于测试远程加载。

使用方式

  1. 打开 Window -> Asset Management -> Addressables -> Hosting
  2. 点击 Create -> Local Hosting
  3. 启用后自动分配端口

Profile 变量配置

http://[PrivateIpAddress]:[HostingServicePort]/[BuildTarget]

注意:Unity 自带托管有时会失效,可用第三方工具如 HFS 搭建 HTTP 服务器。

资源打包理论

打包本质:将可寻址资源打包到 AssetBundle 中,以组为单位进行打包。

打包用途

  • 单机游戏:减小包体,所有 AB 包随游戏发布
  • 网络游戏:热更新,只打包必备资源,其余放远端服务器

打包注意事项

问题 说明 解决方案
场景资源 场景始终单独打包 无需处理,系统自动分离
资源依赖 非可寻址资源被多个组引用会重复打包 将共享资源也设为可寻址
图集问题 不同包使用同一图集会导致重复 图集单独作为可寻址资源

分组建议:同类型、同作用的资源放一起,如角色组、怪物组、UI 组、图集组等。

本地资源发布

流程

  1. 设置组的 Build Path 为 LocalBuildPath
  2. 设置组的 Load Path 为 LocalLoadPath
  3. 点击 Build -> New Build -> Default Build Script

输出位置

  • 默认打包到 Library/com.unity.addressables/aa/Windows/
  • 发布应用时自动复制到 StreamingAssets

注意事项

  • 不建议修改默认的本地构建和加载路径
  • 修改后需要手动将 AB 包复制到 StreamingAssets

远程资源发布

模拟远端流程

  1. 打开 Hosting 窗口,创建 Local Hosting 并启用
  2. 修改 Profile 的 RemoteLoadPath 为 http://[PrivateIpAddress]:[HostingServicePort]/[BuildTarget]
  3. 设置组为 RemoteBuildPath 和 RemoteLoadPath
  4. 勾选 AddressableAssetSettings 的 Build Remote Catalog
  5. 打包后 AB 包生成在 ServerData 文件夹

实际远端流程

  1. 使用 HFS 等工具搭建 HTTP 服务器
  2. 修改 RemoteLoadPath 为实际服务器 URL
  3. 打包后将 ServerData 内容上传到服务器

资源更新

Content Update Restriction 更新限制

模式 说明 适用场景
Can Change Post Release 整包更新,资源变化后重建整个包 大范围资源更新
Cannot Change Post Release 局部更新,变化资源移到更新组 小范围增量更新

整包更新流程

  1. 组设置为 Can Change Post Release
  2. 修改资源后点击 Update a Previous Build
  3. 选择 addressables_content_state.bin 文件
  4. 上传新生成的 AB 包到服务器

局部更新流程

  1. 组设置为 Cannot Change Post Release
  2. 修改资源后点击 Tools -> Check for Content Update Restrictions
  3. 选择变化的资源,应用到更新组
  4. 点击 Update a Previous Build
  5. 上传 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。

关键设计点

  1. 重复加载处理:同一资源重复加载时,检查字典中是否已存在。如果已完成直接回调,未完成则添加到 Completed 事件。

  2. 类型区分:同名不同类型资源需要区分,key 中加入类型名。

  3. 多资源加载:支持 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 包

注意事项

  1. 释放资源不一定立即从内存卸载,因为 AB 包可能还有其他资源在使用
  2. 加载和释放必须配对使用,否则会导致内存泄漏或资源丢失
  3. 建议在场景切换时调用 Resources.UnloadUnusedAssets 清理残留资源
  4. 自定义资源管理器时需要自己维护引用计数,避免复用 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

  1. 启用分析:AddressableAssetSettings → Send Profiler Events
  2. 打开窗口:Window → Asset Management → Addressables → Event Viewer
  3. 观察内容:
    • 加载事件(绿色)和释放事件(红色)是否配对
    • 引用计数是否归零
    • AB 包是否正确卸载

排查步骤

  1. 运行游戏到内存增长的场景
  2. 打开 Event Viewer,观察当前帧的资源引用
  3. 执行本应释放资源的操作(如关闭 UI、切换场景)
  4. 检查 Event Viewer 中是否还有未释放的引用
  5. 定位到具体资源,检查代码中是否遗漏 Release 调用

预防措施

  • 封装资源管理器,统一管理 handle 的生命周期
  • 场景切换时调用 Clear 方法强制清理
  • 使用 using 语句或 try-finally 确保释放
答题示例

内存泄漏常见原因是加载后未释放、handle 丢失或引用计数错误。

排查时使用 Event Viewer 窗口,启用 Send Profiler Events 后观察加载和释放事件是否配对。

具体步骤:运行到问题场景 → 打开 Event Viewer → 执行释放操作 → 检查是否还有未释放引用 → 定位代码遗漏点。

预防措施包括:封装资源管理器统一管理 handle、场景切换时强制清理、确保加载和释放配对调用。

参考文章
  • 19.其他_资源加载相关_引用计数规则
  • 20.其他_窗口相关_事件查看窗口


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏