Unity热更新之Addressables——补充与总结(叁)
五、其他知识补充
(一)根据资源定位信息加载资源
1、之前学过的加载资源方式
- 资源标识类AssetReference
- 动态加载单个资源(资源名/标签名)
- 动态加载多个资源(资源名/标签名/两者组合)
2、加载资源时Addressables做的事情
- 查找资源位置
- 收集依赖项列表
- 下载所需的所有远程AB包(或本地)
- 加AB包加载到内存
- 设置Result资源对象的指
- 更新Status状态变量参数,调用完成时间Completed
加载成功时,可以从Result获得内容,如果加载失败,若启用了AddressableAssetSettings中Diagnostics(诊断)的Log Runtime Exceptions参数,则会打印相关信息
3、资源定位信息
之前动态加载资源时,是有重载方法的,可以传入IResourceLocation(资源定位信息)来进行资源的加载。
而IResourceLocation存放了资源数据、定位、依赖等信息,我们可以获取到这个资源定位信息,再通过这个资源定位信息加载资源(相当于分两步,获取资源定位信息为Addressable做的事情的前两步,通过资源定位信息加载资源相当于后四步)
4、根据名字/标签获取资源定位信息并加载资源
主要方法:Addressables.LoadResourceLocationsAsync
AsyncOperationHandle<IList<IResourceLocation>> handle = Addressables.LoadResourceLocationsAsync("Cube", typeof(GameObject));
handle.Completed += (handle) =>
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
foreach (IResourceLocation item in handle.Result)
{
Debug.Log(item.PrimaryKey); // 资源名
// 利用定位信息加载资源
Addressables.LoadAssetAsync<GameObject>(item).Completed += (handle) =>
{
Instantiate(handle.Result);
};
}
}
else
{
Addressables.Release(handle);
}
};
5、根据两者组合获取资源定位信息并加载资源
使用上面那种方法的重载即可
Addressables.LoadResourceLocationsAsync(
new List<string>() { "Cube","Sphere"},
Addressables.MergeMode.Union,
typeof(GameObject));
6、为什么使用资源定位信息
- 可以获取资源的额外信息
- PrimaryKey:资源主键(资源名)
- InternalId:资源内部ID(资源路径)
- ResourceType:资源类型(Type可获取资源类型名)
- 比如加载多个不同类型资源时,可以通过资源类型进行判断和处理逻辑
- 资源定位信息加载资源并不会增大性能开销,只是分成两步完成而已
(二)异步加载的几种使用方式
1、异步加载知识回顾
handle = Addressable.LoadAssetAsync<>();
handle.Completed += (handle) => { ... }
2、三种异步加载资源方式
- 事件监听(之前学的,就上面这种)
- 协同程序
- 异步函数
3、使用协程进行异步加载
void Start()
{
StartCoroutine(LoadAsset());
}
IEnumerator LoadAsset()
{
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");
if (!handle.IsDone)
yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded)
Instantiate(handle.Result);
else
Addressables.Release(handle);
}
4、通过异步函数async和await加载
- WebGL平台不支持异步函数语法
单任务等待:
void Start()
{
Load();
}
async void Load()
{
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");
await handle.Task;
Instantiate(handle.Result);
}
多任务等待:
async void Load()
{
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");
AsyncOperationHandle<GameObject> handle2 = Addressables.LoadAssetAsync<GameObject>("Sphere");
await Task.WhenAll(handle.Task,handle2.Task);
Instantiate(handle.Result);
Instantiate(handle2.Result);
}
(三)关于Async Operation Handle
1、获取加载进度
获取DownloadStatus结构体,如果已经下载过则结果为0
IEnumerator LoadAsset()
{
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");
while (!handle.IsDone)
{
DownloadStatus status = handle.GetDownloadStatus();
Debug.Log(status.Percent); // 进度
Debug.Log($"{status.DownloadedBytes}/{status.TotalBytes}"); // 下载字节数
yield return null;
}
...
}
2、无类型句柄转换
可以使用无类型的handle接收,并且可以根据需要转换成对应泛型,主要用于管理器进行统一管理handle的时候
// 无类型
AsyncOperationHandle handle = Addressables.LoadAssetAsync<GameObject>("Cube");
// 转换为泛型
AsyncOperationHandle<GameObject> handle2 = handle.Convert<GameObject>();
3、强制同步资源加载
了解即可,不建议使用
AsyncOperationHandle handle = Addressables.LoadAssetAsync<GameObject>("Cube");
handle.WaitForCompletion(); // 强行等待资源加载结束,会卡线程
(四)自定义更新目录和下载AB包
1、目录文件的作用
目录文件本质时Json文件和一个Hash文件,内容如下:
- Json文件
- 加载AB包、图集、资源、场景、实例化对象所用的脚本(会通过反射加载)
- AB包所有资源类型对应的类(通过反射加载)
- AB包对应路径
- 资源的Path名
- 等等
- Hash文件
- 存储目录文件对应的Hash码,用于判断文件是否变化
- 更新时本地文件hash码和远端目录hash码进行对比,不同则更新目录文件
2、手动更新目录
手动更新目录时,建议在设置中关闭自动更新(勾选AddressableAssetSettings的Only update catalogs manually)
- 调用如下方法自动检查所有目录是否有更新,并且自动更新目录:
Addressables.UpdateCatalogs().Completed += (handle) =>
{
Addressables.Release(handle); // 更新完毕记得释放资源
};
- 也可以获取变化的目录,再更新目录:
Addressables.CheckForCatalogUpdates().Completed += (handle) =>
{
if (handle.Result.Count > 0)
{
Addressables.UpdateCatalogs(handle.Result).Completed += (obj) =>
{
// 更新完毕记得释放资源
Addressables.Release(handle);
Addressables.Release(obj);
};
}
};
上面这些方法都有第二个bool参数,代表是否自动释放句柄,这样就可以不用自己手动释放
3、预加载包
可以提前对资源进行加载,避免游戏过程中资源来不及加载。
- 先通过
Addressables.GetDownloadSizeAsync获取下载的包大小,只有大于0才说明没加载过,需要加载 - 再通过
Addressables.DownloadDependenciesAsync加载AB包所有需要的依赖 - 加载时可以获取进度条信息
IEnumerator LoadAsset()
{
// 1、获取下载包大小(可以传资源名/标签名或两者结合)
AsyncOperationHandle<long> handleSize = Addressables.GetDownloadSizeAsync("Cube");
yield return handleSize;
// 2、预加载
if (handleSize.Result > 0) // 大于0说明没有下载过
{
// 加载所有依赖的AB包(可以传资源名/标签名或两者结合)
AsyncOperationHandle handle = Addressables.DownloadDependenciesAsync("Cube");
// 3、进度条,可以打印进度条信息
while (!handle.IsDone)
{
DownloadStatus status = handle.GetDownloadStatus();
Debug.Log(status.Percent);
yield return null;
}
// 释放句柄
Addressables.Release(handle);
}
}
4、总结
游戏开始前,可以显示加载界面,对一些资源进行预加载,也可以手动对目录进行更新
(五)引用计数规则
1、概念
加载可寻址资源时,Addressabels会在内部帮助我们进行引用计数,管理内存,避免内存泄漏。使用资源时,引用计数+1;释放资源时,引用计数-1。当引用计数为0,则可以卸载该资源。
为了避免内存泄漏,必须保证加载和卸载资源时配对使用。
AB包也有引用计数,表示该AB包被使用的次数(因为Addressables也把它们视为可寻址资源)
释放的资源不一定立即从内存中卸载。在卸载它所属AB包前,是不会释放资源所占内存的。可以使用Resources.UnloadUnusedAssets卸载AB包没被卸载但已经没被使用的资源。(简单来说就是AB包里的资源,需要等AB包没被使用才能被卸载,只要AB包里面还有一个资源在使用,其他资源都不会卸载)
2、测试
可以自己测试下引用计数,测试时,请用第三种方式加载AB包(即Play Mode Script设置为Use Existing Build),这样才能看到效果
每次调用Addressables.LoadAssetAsync,引用计数会+1
每次调用Addressables.Release(handle)释放句柄时,对应资源引用计数会-1
当引用计数为0时,资源会被卸载
3、自己的小测试
Addressables.LoadAssetAsync加载单个资源时的句柄被Release后,句柄对应的资源引用计数-1
Addressables.LoadAssetsAsync加载多个资源时的句柄被Release后,句柄中每个资源的引用计数都-1
(六)事件查看窗口
1、作用
可以监视可寻址资源的资源内存管理,查看可寻址资源对性能的影响和检查有没有释放的资源
- 显示应用程序合适加载和卸载资源
- 显示所有可寻址系统操作的引用计数
- 显示应用程序帧率和分配的内存总量近似图
2、打开窗口
使用前需要打开AddressablesAssetSettings中的事件发送开关(Send Profiler Events)
- 方式一:Window -> Asset Management -> Addressables -> Event Viewer
- 方式二:Addressables Groups -> Window -> Event Viewer
3、使用
Event Viewer 工具现在已经被弃用,被新的 Addressables Profiler Module 取代了,这里韩式继续写旧版的Event Viewer功能。
左上角:
- Clear Event:清除所有记录的帧
- Unhide All Hidden Events:显示所有你隐藏的事件内容
右上角:
- Frame:当前所在帧
- Current:选中当前所在帧
中央部分:
- FPS:应用的帧率
- MonoHeap:正在使用的托管堆内存量
- Event Counts:事件计数,某一帧中发生的可寻址事件的数量
- Instantiation Counts:实例化计数,某一帧中
Addressables.InstantiateAsync的调用数量 - 线性图标:显示统计的什么时候加载释放资源的额信息
- Event相关:当前帧中发生的可寻址操作的事件
4、关于新版本
新版本只能使用Profiler,下面讲下怎么启用
- Edit → Preferences → Addressables,勾选 “Debug Build Layout”,这样每次构建Addressables都会生成报告文件,构建时生成的报告文件位于
<项目>/Library/com.unity.addressables/buildReports/ - 在Addressables的Play Mode Script中,选择 “Use Existing Build” 模式
- 重新构建Addressables
- 打开窗口:Window > Analysis > Profiler,运行后即可使用
(七)分析窗口
1、作用
一种收集项目可寻址布局信息的工具,它是一种信息工具,可以让我们对可寻址文件布局做出更明智的决定。
简单来说就是检测我们AB包布局是否合理
2、打开窗口
- 方式一:Window -> Asset Management -> Addressables -> Analyze
- 方式二:Addressables Groups -> Window -> Analyze
3、使用

上方三个按钮:
- Analyze Selected Rules:分析选定的规则
- Clear Selected Rules:清除上一次选定的规则的信息
- Fix Selected Rules:修复选定的规则
下方内容:
- Analyze Rules:分析规则
- Fixable Rules:可修复的规则(可分析可修复)
- Check Duplicate Bundle Dependencies:检查重复的AB包依赖项。
主要处理的问题:资源a和b,都用了资源c,但a、b是可寻址资源而c不是。
建议自己处理问题,因为某些特殊情况它也会认为有问题:如FBX有多个网格信息a和b,a在包A,b在包B,但此时它也认为有重复问题。
- Check Duplicate Bundle Dependencies:检查重复的AB包依赖项。
- Unfixable Rules:不可修复规则(只可以分析)
- Check Resources to Addressable Duplicate Dependencies:检查可寻址重复依赖项的资源。
主要处理的问题:同时出现在可寻址资源和应用程序构建的资源中,如资源a是可寻址资源,但它还出现在Resources、StreamingAssets等特殊文件夹中,最终会被打包出去 - Check Scene to Addressable Duplicate Dependencies:检查场景中的可寻址重复依赖项的资源。
主要处理的问题:同时出现在可寻址资源和某一个场景中,如资源a,它是可寻址资源但它直接出现在某一个场景中。 - Bundle Layout Preview:AB包布局预览
- Check Resources to Addressable Duplicate Dependencies:检查可寻址重复依赖项的资源。
- Fixable Rules:可修复的规则(可分析可修复)
选中某一个规则后,可以点击上方按钮对该规则进行处理。
当然还可以自己定义规则,需要可以去了解一下
(八)构建布局报告
1、作用
提供了有关可寻址资源的构建打包的详细信息和统计信息。包括:
- AB包描述
- 每个资源和AB包大小
- 解析作为依赖项隐式包含在AB包中的不可寻址资源
- AB包的依赖关系
2、如何查看
Edit -> Preferences -> Addressables,启用Debug Build Layout
之后构建打包可寻址资源后,可以在<项目>/Library/com.unity.addressables/buildReports/找到相关文件,可以查看信息
(九)常见问题总结
1、多包策略还是大包策略
- 多包策略问题:每个包都有内存开销,加载过多包可能带来更多内存消耗;并且并发下载时,包小而多,则会消耗更多时间下载;目录文件会变大,因为要记录更多包信息。产生重复资源可能性增大,如多个包引用同一资源,但该资源不是可寻址资源。
- 大包策略:包体过大下载失败时,需要重新下载,体验较差;并且大包资源数量更多,即使里面99个资源不用了,但只要还有一个资源再用,这个打包也不会卸载,会造成内存浪费
选择时只需要根据项目需求来合理安排分组打包即可,权衡两者利弊。
2、压缩方式
- LZ4:基于块的压缩,提供了加载文件的能力,加载资源时不用全部加载AB包,只加载使用的内容,相对LZMA来说更节约内存
- LZMA:不建议使用在本地内容中,因为虽然包体压缩最小,但是加载(解压)最慢,主要是为了节约远程下载时间和包体大小。
根据情况选择,但是LZ4相对比较优秀,默认选择就行
3、减小目录文件大小
- 方式一,压缩本地目录:AddressableAssetSettings -> Catalog -> Compress Local Catalog
- 方式二,禁用内置场景和资源:默认Addressables提供了从Resources等内置资源文件夹加载资源和内置场景的方法。但是如果你不想使用Addressables加载这些非寻址资源,而是用老方法加载,则可以取消。
在Addressables Groups的Built In Data分组中,右侧Resources and Built In Scenes参数的两个选项可以取消勾选
4、AB包限制
AB包最大限制,老版本不支持大于4G的包,新版本没有这个限制,但是为了兼容性,建议控制在4G以下
5、活用Groups View的两个功能
Addressables Groups -> Tools -> Groups View中,有两个功能可以打开:
- Show Sprite and Subobject Addresses:当该窗口内容特别多时,禁用它可以取消显示资源的子物体,提升窗口加载性能
- Group Hierarchy with Dashes:启用后,可以在组命名时带上
-来进行层级显示,内容多时可以便于查看
六、总结
1、几种常用资源加载方式
- Resources:应用程序发布后不能动态修改,本地
- AssetBundle:减小包体大小、热更新
- Addressables:基于AssetBundle,帮助我们管理AssetBundle
2、选择
Resources适合小游戏、单机游戏;AssetBundle和Addressables适合商业游戏,老项目或者迭代项目用AssetBundle,新项目可以尝试Addressables,使用更方便