众所周知,Addressables的资源加载全部都是异步完成的。它主要通过两个接口:Addressables.LoadAssetAsync和Addressables.LoadAssetsAsync。我们先来看一下LoadAssetAsync接口:
public static AsyncOperationHandle<TObject> LoadAssetAsync<TObject>(object key)
{
return m_Addressables.LoadAssetAsync<TObject>(key);
}
public static AsyncOperationHandle<TObject> LoadAssetAsync<TObject>(IResourceLocation location)
{
return m_Addressables.LoadAssetAsync<TObject>(location);
}
其中第二个重载的参数是 IResourceLocation,这个我们在另一篇文章讨论。今天我们重点看一下第一个更为通用的接口。第一个接口接收的是一个object类型的参数。它可以是如下几种类型:
• Address: 一个string类型,表示资源地址。
• Label: 一个string类型,表示资源所属的标签。
• AssetReference: 一个AssetReference的实例
可以看到,这个接口实际上是将参数直接传递给了m_Addressables.LoadAssetAsync。这里我们看一下m_Addressables的定义:
internal static AddressablesImpl m_AddressablesInstance = new AddressablesImpl(new LRUCacheAllocationStrategy(1000, 1000, 100, 10));
static AddressablesImpl m_Addressables
{
get
{
#if UNITY_EDITOR
if (EditorSettings.enterPlayModeOptionsEnabled && reinitializeAddressables)
{
reinitializeAddressables = false;
m_AddressablesInstance.ReleaseSceneManagerOperation();
m_AddressablesInstance = new AddressablesImpl(new LRUCacheAllocationStrategy(1000, 1000, 100, 10));
}
#endif
return m_AddressablesInstance;
}
}
由此可见[color=blue]m_AddressablesInstance[/color]就是AddressablesImpl的一个实例。而它的构造参数则是一个IAllocationStrategy的派生类,顾名思义这是一个内存管理策略对象。
来看一下AddressablesImpl.LoadAssetAsync接口都做了什么
public AsyncOperationHandle<TObject> LoadAssetAsync<TObject>(object key)
{
if (ShouldChainRequest) // ShouldChainRequest是AddressablesImpl的一个属性,若当前已经初始化完成则返回false,若需要进行初始化则返回true
// ChainOperation也是AddressablesImpl的一个属性,负责返回当前所需要在加载操作之前进行的行为
// LoadAssetWithChain负责构建一个链式操作,该链式操作会在ChainOperation结束之后再一次调用LoadAssetAsync
// TrackHandle则负责给LoadAssetWithChain返回的AsyncOperationHandle对象设置回调
return TrackHandle(LoadAssetWithChain<TObject>(ChainOperation, key));
// EvaluateKey对key进行处理,如果key是IKeyEvaluator类型则取它的RuntimeKey属性,AssetReference则是在这里被转化为AssetReference.m_AssetGUID(string类型)
key = EvaluateKey(key);
IList<IResourceLocation> locs;
// 读取TObject的类型,如果是泛型则读取实际的类型
var t = typeof(TObject);
if (t.IsArray)
t = t.GetElementType();
else if (t.IsGenericType && typeof(IList<>) == t.GetGenericTypeDefinition())
t = t.GetGenericArguments()[0];
// 遍历当前所有的ResourceLocator,调用IResourceLocator.Locate接口查询资源路径,获得对应的IResourceLocation列表
foreach (var locatorInfo in m_ResourceLocators)
{
var locator = locatorInfo.Locator;
if (locator.Locate(key, t, out locs))
{
foreach (var loc in locs)
{
// 遍历locs(IResourceLocation列表),查询对应的Provider(若不存在则不支持该资源)
var provider = ResourceManager.GetResourceProvider(typeof(TObject), loc);
if (provider != null)
// 调用ResourceManager.ProvideResource进行加载
return TrackHandle(ResourceManager.ProvideResource<TObject>(loc));
}
}
}
// 执行到这里说明没有找到对应该key的资源,返回一个带有异常的已完成的AsyncOperationHandle对象
return ResourceManager.CreateCompletedOperationWithException<TObject>(default(TObject), new InvalidKeyException(key, t));
}
从上面的代码里可以看到,加载的核心逻辑在[color=blue]ResourceManager.ProvideResource[/color]接口,让我们来看一下:
public AsyncOperationHandle<TObject> ProvideResource<TObject>(IResourceLocation location)
{
// 调用非泛型版本的ProvideResource
AsyncOperationHandle handle = ProvideResource(location, typeof(TObject));
// 将handle转化成泛型版本(带类型)
return handle.Convert<TObject>();
}
/// <summary>
/// Load the <typeparamref name="TObject"/> at the specified <paramref name="location"/>.
/// </summary>
/// <returns>An async operation.</returns>
/// <param name="location">Location to load.</param>
/// <param name="releaseDependenciesOnFailure">When true, if the operation fails, dependencies will be released.</param>
/// <typeparam name="TObject">Object type to load.</typeparam>
private AsyncOperationHandle ProvideResource(IResourceLocation location, Type desiredType = null, bool releaseDependenciesOnFailure = true)
{
if (location == null)
throw new ArgumentNullException("location");
IResourceProvider provider = null;
if (desiredType == null)
{
// 如果传入的desiredType是空的,则通过GetResourceProvider来查询provider上的默认类型
provider = GetResourceProvider(desiredType, location);
if (provider == null)
{
var ex = new UnknownResourceProviderException(location);
return CreateCompletedOperationInternal<object>(null, false, ex, releaseDependenciesOnFailure);
}
desiredType = provider.GetDefaultType(location);
}
// 检查当前是否已有正在加载的异步操作,若有则增加引用计数并返回
IAsyncOperation op;
var key = new LocationCacheKey(location, desiredType);
if (m_AssetOperationCache.TryGetValue(key, out op))
{
op.IncrementReferenceCount();
return new AsyncOperationHandle(op, location.ToString());;
}
// 查找或生成provType
// provType是ProviderOperation<T>类型
// ProviderOperation负责实际的加载与解析操作
// m_ProviderOperationTypeCache是该类型的一个缓冲池,用来防止多次调用MakeGenericType来生成ProviderOperation<T>类型
Type provType;
if (!m_ProviderOperationTypeCache.TryGetValue(desiredType, out provType))
m_ProviderOperationTypeCache.Add(desiredType, provType = typeof(ProviderOperation<>).MakeGenericType(new Type[] { desiredType }));
// 调用CreateOperation创建一个加载操作
op = CreateOperation<IAsyncOperation>(provType, provType.GetHashCode(), key, m_ReleaseOpCached);
// Calculate the hash of the dependencies
int depHash = location.DependencyHashCode;
// 如果该请求有依赖项,则调用ProvideResourceGroupCached来加载所有的依赖项,并生成一个依赖操作depOp
var depOp = location.HasDependencies ?
ProvideResourceGroupCached(location.Dependencies, depHash, null, null, releaseDependenciesOnFailure) :
default(AsyncOperationHandle<IList<AsyncOperationHandle>>);
// 获取类型对应的provider
if (provider == null)
provider = GetResourceProvider(desiredType, location);
// 初始化ProviderOperation
((IGenericProviderOperation)op).Init(this, provider, location, depOp, releaseDependenciesOnFailure);
// 开始加载操作
var handle = StartOperation(op, depOp);
handle.LocationName = location.ToString();
if (depOp.IsValid())
depOp.Release();
return handle;
}
可以看到[color=blue]ResourceManager.ProvideResource[/color]内部是通过各个[color=blue]ProviderOperation[/color]来进行实际的加载操作的。那么看完了LoadAssetAsync的加载代码,现在让我们来看一下LoadAssetsAsync,也就是同时加载多个资源的版本。
/// <typeparam name="TObject">The type of the assets.</typeparam>
/// <param name="keys">Key的集合</param>
/// <param name="callback">每加载完一个资源所会调用的回调</param>
/// <param name="mode">MergeMode枚举类型,用来表示多个Key是如何决定加载哪些资源的</param>
/// <param name="releaseDependenciesOnFailure">bool类型,决定了有资源加载失败时,如何处理其他资源及依赖项
/// 当所有资源都加载成功时,该参数会被忽略。
/// 当有资源加载失败时:
/// 若为true,则所有资源及依赖项会被释放,并且AsyncOperationHandle.Result会被设置为null。
/// 若为false,则AsyncOperationHandle.Result会被设置为一个长度跟资源数相同List对象,
/// 其中加载失败的资源会被置为null,成功的会设为一个有效的TObject对象。
/// 当有资源加载失败时,不管releaseDependenciesOnFailure是true还是false,
/// AsyncOperationHandle.Status都会被置为AsyncOperationStatus.Failed
/// 当有资源加载失败时,若releaseDependenciesOnFailure,你不需要释放返回的AsyncOperationHandle实例
/// 否则你不能忘了释放它
public static AsyncOperationHandle<IList<TObject>> LoadAssetsAsync<TObject>(IEnumerable keys, Action<TObject> callback, MergeMode mode, bool releaseDependenciesOnFailure)
{
return m_Addressables.LoadAssetsAsync(keys, callback, mode, releaseDependenciesOnFailure);
}
这里有一个比较重要的参数“[color=red]mode[/color]”。每一个Key都会解析出一个ResourceLocation集合,当这些集合有互相交叉的部分的话,[color=red]mode[/color]参数就决定了怎么生成最终的结果集。它一共有3个类型:
• [color=red]MergeMode.UseFirst[/color]: 不管后面的集合是什么,永远只选择第一个集合
• [color=red]MergeMode.Union[/color]: 最终的结果将是这些集合的一个并集,包含所有的结果。
• [color=red]MergeMode.Intersection[/color]: 最终的结果是这些集合的交集,只包含所有集合共有的部分。
这些不同的[color=blue]AddressablesImpl.LoadAssetsAsync[/color]重载接口最终都会调用到同一个接口:
public AsyncOperationHandle<IList<TObject>> LoadAssetsAsync<TObject>(IList<IResourceLocation> locations, Action<TObject> callback, bool releaseDependenciesOnFailure)
{
return TrackHandle(ResourceManager.ProvideResources(locations, releaseDependenciesOnFailure, callback));
}
可以看到这个接口直接调用了[color=blue]ResourceManager.ProvideResources[/color],那么ProvideResources又干了什么呢?继续看代码:
/// <returns>AsyncOperationHandle实例</returns>
/// <param name="locations">所有需要加载资源的ResourceLocation</param>
/// <param name="releaseDependenciesOnFailure">发生失败时是否释放已加载的资源</param>
/// <param name="callback">每加载一个资源都会被调用的回调</param>
/// <typeparam name="TObject">所要加载资源的类型</typeparam>
public AsyncOperationHandle<IList<TObject>> ProvideResources<TObject>(IList<IResourceLocation> locations, bool releaseDependenciesOnFailure, Action<TObject> callback = null)
{
// 如果locations为空,则创建一个已完成的操作(CompletedOperation)
if (locations == null)
return CreateCompletedOperation<IList<TObject>>(null, "Null Location");
// 创建一个通用的支持AsyncOperationHandle参数的回调
Action<AsyncOperationHandle> callbackGeneric = null;
if (callback != null)
{
callbackGeneric = (x) => callback((TObject)(x.Result));
}
// 调用ProvideResourceGroupCached来加载所有locations对应的资源
// ProvideResourceGroupCached是个递归加载的过程
// 它会分别调用ResourceManager.ProvideResource去加载每一个资源(包括它们的依赖项)
var typelessHandle = ProvideResourceGroupCached(locations, CalculateLocationsHash(locations, typeof(TObject)), typeof(TObject), callbackGeneric, releaseDependenciesOnFailure);
// 调用CreateChainOperation创建一个ChainOperationTypelessDepedency对象并启动(StartOperation)
// 该对象会在typelessHandle结束后调用后面的callback参数
var chainOp = CreateChainOperation<IList<TObject>>(typelessHandle, (resultHandle) =>
{
// 从AsyncOperationHandle转化成模板版本
AsyncOperationHandle<IList<AsyncOperationHandle>> handleToHandles = resultHandle.Convert<IList<AsyncOperationHandle>>();
var list = new List<TObject>();
Exception exception = null;
if (handleToHandles.Status == AsyncOperationStatus.Succeeded)
{
// 所有资源加载成功,将结果添加到list中
foreach (var r in handleToHandles.Result)
list.Add(r.Convert<TObject>().Result);
}
else
{
// 有资源加载失败,如果releaseDependenciesOnFailure为false则表示需要添加加载成功的资源到list中
// 不成功的资源置为default(TObject)
bool foundSuccess = false;
if (!releaseDependenciesOnFailure)
{
foreach (AsyncOperationHandle handle in handleToHandles.Result)
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
list.Add(handle.Convert<TObject>().Result);
foundSuccess = true;
}
else
list.Add(default(TObject));
}
}
// 根据是否有加载成功的资源,设置不同的exception,以及是否将list置空
if (!foundSuccess)
{
list = null;
exception = new ResourceManagerException("ProvideResources failed", handleToHandles.OperationException);
}
else
{
exception = new ResourceManagerException("Partial success in ProvideResources. Some items failed to load. See earlier logs for more info.", handleToHandles.OperationException);
}
}
// 创建CompletedOperation并执行
return CreateCompletedOperationInternal<IList<TObject>>(list, exception == null, exception, releaseDependenciesOnFailure);
}, releaseDependenciesOnFailure);
// chain operation holds the dependency
// 释放掉typelessHandle句柄,因为chainOp已经持有了它的引用
typelessHandle.Release();
return chainOp;
}
所以其实[color=blue]ResourceManager.ProvideResources[/color]的本质还是通过[color=blue]ResourceManager.ProvideResourceGroupCached[/color]对每一个[color=red]location[/color]调用了[color=blue]ResourceManager.ProvideResource[/color]来实现对多个资源的加载的,然后最上层使用了一个ChainOperation来负责在所有这些资源加载完之后调用回调来处理结果。
至此,Addressables的加载流程已经全部分析完毕,可以看到在Addressables的加载中,各种Operation和Provider起了很大的作用。那么这些Operation和Provider分别是做什么的呢?让我们另开个文章来具体分析一下。