unity
使用Addressables
进行资源管理很方便但是有一个很值得诟病的问题, 那就是catalog
文件过大, 对于一个持续运营的项目来说catalog
文件可能超过10M, 导致初始化Addressables
时间较长, 峰值内存上涨明显.
本文通过分析catalog
文件中主要的内容占用和内存开销, 提出一些可行的(已实践)方案.
一. catalog
相关的开销分析
catalog
开销主要来自两个部分:
- 初始化
Addressables
的时间开销, 使用Addressables
需要确保catalog
加载完成, 对于部分网络情况不佳的用户造成卡顿较长的体验; - 加载
catalog
文件的内存开销, 使用json
格式的Addressables
加载时会产生两倍于自身大小的内存开销.
加载catalog
的时间开销和网络情况密切相关, 实际测试下来一个1M
的catalog
文件经过传输和加载到内存, 整体时间超过1s
. 主要的开销来源是文件的大小.
内存上的开销主要是加载文本本身和反序列化数据之后的结果上的内存开销.
1. catalog
本身大小开销分析
catalog
文件本身是一个json
文件, 主要结构如下:
本身实际是ContentCatalogData
的json
序列化结果. 其中包含的数据是以下几类数据:
属性 | 数据含义 |
---|---|
m_LocatorId |
Addressables 支持多catalog,所以添加了区分不同catalog的ID |
m_InstanceProviderData |
Instantiate 方法使用的Provider 的序列化结果 |
m_SceneProviderData |
加载Scene 使用的Provider 序列化数据 |
m_ResourceProviderData |
加载资源使用的Provider 的序列化结果 |
m_ProviderIds |
Provider 的类名称 |
m_InternalIds |
资源的路径列表, 是AssetPath 的Base64 加密结果 |
m_KeyDataString |
Key 的二进制序列化结果 |
m_BucketDataString |
|
m_EntryDataString |
资源的Entry 的序列化结果,包含资源的类型,资源的以来等 |
m_resourceTypes |
资源的类型 |
m_InternalIdPrefixes |
InternalId 列表的前缀优化数据 |
从json
文件里面能看到其中比较大的主要是m_KeyDataString
m_InternalIds
, m_EntryDataString
分别指向了资源的路径, 资源的key
和资源的加载信息其中每一段的数据的大小如下:
scss
catalog.json 16.30MB (3.20MB compressed)
43282 internalIds
86296 Keys:5.88MB
86296 Buckets:2.76MB
106176 Entries:3.78MB
731 Extras:802.29KB
2. catalog
加载内存开销分析
// TODO
二. 可行的catalog
优化方案
1.剔除locations
上面分析中发现很多的开销是来在Locations也就是资源的数量, 如果假如到catalog中的资源数量较多, 自然catalog会明显增大. 那么什么是需要添加到catalog中的什么是不需要的呢? 这里项目不一样判断的方法也不一样, 这里设计了一个简单的配置正则项去筛选需要加到Catalog中的Locations用于过滤需要保留的locations. 经过过滤之后能减少2/3的location数量, 能减少大约1/3的大小.
剔除Location
可能会导致label
丢失, 比如你给一个路径下的资源都标记为lableA
,但是所有的资源Location
被删除了导致label
丢失. 使用这个方案请确保所有的通过Addressables
加载的资源确实被保留了.
代码如下
csharp
public class LocationFilterConfig : ScriptableObject
{
protected virtual void Init()
{
}
// 用于子类实现自己的忽略功能
protected virtual bool IsIgnored(ContentCatalogDataEntry entry)
{
return false;
}
// 过滤的接口
public void FilterLocation(List<ContentCatalogDataEntry> locations, ref List<ContentCatalogDataEntry> output)
{
Init();
foreach(ContentCatalogDataEntry location in locations)
{
if(!location.ResourceType.IsAssignableFrom(typeof(IAssetBundleResource)) && IsIgnored(location))
{
continue;
}
output.Add(location);
}
}
}
// 具体实现
public class ResConfigLocationFilterConfig : LocationFilterConfig
{
[SerializeField]
private string[] m_includedAssets;
private Regex[] m_includedAssetsRegex;
private HashSet<string> m_internalIds;
protected override void Init()
{
m_includedAssetsRegex = null;
if (m_includedAssets != null)
{
m_includedAssetsRegex = new Regex[m_includedAssets.Length];
for(int i = 0;i < m_includedAssets.Length;i++)
{
m_includedAssetsRegex[i] = new Regex(m_includedAssets[i]);
}
}
}
// 使用正则项对资源路径进行过滤
protected override bool IsIgnored(ContentCatalogDataEntry entry)
{
if (m_includedAssetsRegex != null)
{
foreach (Regex regex in m_includedAssetsRegex)
{
if(regex.IsMatch(entry.InternalId))
{
return false;
}
}
}
return true;
}
}
2.剔除location
类型
对于同一个资源, 存在多个Entry
, 其中不同的key
, 不同的资源类型都会生成一个新的数据, 所以会导致同一个资源数据多很多. 这里主要是剔除key
和资源类型:
2.1 剔除key
剔除key
可能会导致加载资源时无法找到对应的资源而加载失败, 确保你的剔除手段是合理的
剔除key
的方法很简单, 在AssetGroup
里面勾选是否包含部分信息.其中分别包含的信息是是否需要使用Addresses
加载资源, 是否会使用AssetReference
加载资源以及是否会通过Label
加载资源
2.2 剔除资源类型
对于一个资源来说, 他的资源类型有很多, 在Catalog中会生成不同类型的Entry, 比如TextAsset
的资源不仅仅会存在一个TextAsset
的Entry
, 也会产生UnityEngine.Object
资源的Entry
. 这里也能按照类型剔除部分的资源类型生成的Entry
.
csharp
[CreateAssetMenu(fileName = "TypeLocationFilterConfig", menuName = "Addressables/Location Filter/Type")]
public class LocationFilterConfigType : LocationFilterConfig
{
[SerializeField]
private string[] m_types;
protected override bool IsIgnored(ContentCatalogDataEntry entry)
{
string typeName = entry.ResourceType.FullName;
foreach(string type in m_types)
{
if(type == typeName)
{
return true;
}
}
return false;
}
}
3.剔除依赖
可能会导致资源加载失败, 请记得实现检查工具
依赖的数据是catalog中很大的部分, 尤其是对于依赖关系复杂的项目, 依赖的数据可能会比真实数据还多, 这里推荐使用编辑器下的资源依赖去替换使用Assetbundle
计算出来的依赖关系, 减少资源的依赖数量
三.总结
Addressables
是一个广泛使用的资源管理库, 使用默认的方式能确保完全不会出问题, 但是不同项目有不同的实际情况, 在做库开发时适用性应该要大于效率,但是项目实际使用的情况下应当是效率大于适用.