源码-资源提供者(Provider 体系)
篇章 :05-源码深度-运行时核心模块 阅读时间 :约 30 分钟 前置知识:了解寻址系统
一、引言
本章将深入解析 YooAsset 资源提供者(Provider 体系)的源码实现。Provider 是 YooAsset 资源加载的核心,封装了不同类型资源的加载逻辑。YooAsset 提供了多种 Provider 类型,包括 AssetProvider、SceneProvider、ComponentProvider 等。
Provider 体系的设计目标是提供灵活、可扩展的资源加载能力。本章将从源码层面深入解析 Provider 体系的设计和实现。
二、Provider 概述
2.1 Provider 的概念
Provider 是 YooAsset 中负责加载单个资源的核心组件:
Provider 的定义:
Provider 是一个抽象类,封装了资源加载的所有逻辑。每个 Provider 对应一个资源的加载。
Provider 的特点:
- 单一职责:每个 Provider 只负责一个资源
- 状态管理:Provider 维护资源加载的状态
- 引用计数:Provider 内置引用计数管理
- 异步加载:Provider 支持异步加载资源
Provider 的作用:
- 接收资源加载请求
- 创建和加载 Bundle
- 从 Bundle 中加载资源
- 管理资源生命周期
2.2 Provider 状态机
Provider 内部维护了一个状态机:
css
Provider 状态机
├── None:初始状态
├── Loading:加载中
├── Loaded:已加载
├── Releasing:释放中
└── Destroyed:已销毁
None 详解:
None 是 Provider 的初始状态,Provider 创建后即进入 None 状态。
Loading 详解:
Loading 是 Provider 的加载中状态。Provide 方法被调用后进入 Loading 状态。
Loaded 详解:
Loaded 是 Provider 的已加载状态。资源加载完成后进入 Loaded 状态。
Releasing 详解:
Releasing 是 Provider 的释放中状态。UnProvide 方法被调用后进入 Releasing 状态。
Destroyed 详解:
Destroyed 是 Provider 的已销毁状态。资源销毁后进入 Destroyed 状态。
2.3 Provider 状态详解
csharp
public enum EProviderStatus
{
None = 0, // 初始状态
Loading = 1, // 加载中
Loaded = 2, // 已加载
Failed = 3, // 加载失败
Destroyed = 4, // 已销毁
}
状态转换详解:
- None → Loading:调用 Provide 方法
- Loading → Loaded:资源加载成功
- Loading → Failed:资源加载失败
- Loaded/Failed → Destroyed:调用 Destroy 方法
2.4 Provider 创建
csharp
public abstract class ProviderBase
{
public string Location { get; }
public EProviderStatus Status { get; protected set; }
public float Progress { get; protected set; }
public object AssetObject { get; protected set; }
public int RefCount { get; protected set; }
public abstract void Provide();
public abstract void UnProvide();
public abstract void Update();
}
Location 详解:
Location 是 Provider 加载的资源地址。
Status 详解:
Status 是 Provider 的当前状态。
Progress 详解:
Progress 是 Provider 的加载进度。
AssetObject 详解:
AssetObject 是 Provider 加载的资源对象。
RefCount 详解:
RefCount 是 Provider 的引用计数。
三、Provider 创建/获取
3.1 Provider 的创建
Provider 的创建由 ResourceManager 负责:
csharp
public class ResourceManager
{
public AssetProvider CreateAssetProvider(string location, Type assetType)
{
AssetProvider provider = new AssetProvider(this, location, assetType);
return provider;
}
}
Provider 实例化详解:
ResourceManager 根据资源的类型创建对应的 Provider。
Provider 参数详解:
创建 Provider 时需要传入 ResourceManager、location、assetType 等参数。
3.2 Provider 的获取
csharp
public class ResourceManager
{
private Dictionary<string, ProviderBase> _providerCache = new Dictionary<string, ProviderBase>();
public ProviderBase GetOrCreateProvider(string location, Type assetType)
{
string key = $"{location}_{assetType.Name}";
if (_providerCache.TryGetValue(key, out var provider))
{
provider.IncreaseRefCount();
return provider;
}
provider = CreateAssetProvider(location, assetType);
_providerCache[key] = provider;
provider.IncreaseRefCount();
return provider;
}
}
Provider 复用详解:
GetOrCreateProvider 方法会先在缓存中查找已存在的 Provider。如果存在则复用,否则创建新的。
引用计数增加详解:
每次获取 Provider 时,引用计数加 1。
3.3 Provider 的销毁
csharp
public class ResourceManager
{
public void DestroyProvider(ProviderBase provider)
{
string key = provider.Location;
_providerCache.Remove(key);
provider.Destroy();
}
}
Provider 销毁详解:
当 Provider 的引用计数为 0 时,会从缓存中移除并销毁。
四、Provider 加载/卸载
4.1 加载流程
Provider 的加载流程如下:
markdown
Provider 加载流程
1. 接收加载请求
2. 增加引用计数
3. 启动加载流程
a. 创建 BundleLoader
b. 加载 Bundle
c. 加载依赖的 Bundle
d. 从 Bundle 中加载资源
4. 加载完成
5. 触发回调
接收加载请求详解:
用户调用 LoadAssetAsync 时,ResourceManager 会创建或复用 Provider。
增加引用计数详解:
每次加载请求会增加 Provider 的引用计数。
启动加载流程详解:
Provide 方法启动加载流程。
Bundle 加载详解:
Provider 创建 BundleLoader 加载 Bundle。
资源加载详解:
从 Bundle 中加载资源。
4.2 加载实现
csharp
public class AssetProvider : ProviderBase
{
private BundleLoader _bundleLoader;
private List<BundleLoader> _dependLoaders;
private AssetBundleRequest _request;
public override void Provide()
{
Status = EProviderStatus.Loading;
Progress = 0;
// 1. 获取 AssetInfo
var assetInfo = Package.GetAssetInfo(Location);
if (assetInfo == null)
{
Status = EProviderStatus.Failed;
Error = "AssetInfo not found";
return;
}
// 2. 加载依赖 Bundle
_dependLoaders = new List<BundleLoader>();
foreach (var depBundleID in assetInfo.DependIDs)
{
var depBundle = Package.GetBundleInfo(depBundleID);
var loader = Package.ResourceManager.LoadBundle(depBundle);
_dependLoaders.Add(loader);
}
// 3. 加载主 Bundle
var mainBundle = Package.GetBundleInfo(assetInfo.BundleID);
_bundleLoader = Package.ResourceManager.LoadBundle(mainBundle);
}
public override void Update()
{
if (Status != EProviderStatus.Loading) return;
// 计算进度
float total = _dependLoaders.Count + 1;
float current = 0;
foreach (var loader in _dependLoaders)
{
if (loader.IsDone) current++;
}
if (_bundleLoader.IsDone) current++;
Progress = current / total;
// 检查是否完成
if (Progress < 1) return;
// 加载资源
_request = _bundleLoader.AssetBundle.LoadAssetAsync(AssetInfo.AssetPath, AssetType);
}
}
加载依赖 Bundle 详解:
加载资源前,先加载所有依赖的 Bundle。
加载主 Bundle 详解:
加载资源所属的主 Bundle。
进度计算详解:
进度 = 已加载的 Bundle 数 / 总 Bundle 数。
资源加载详解:
所有 Bundle 加载完成后,从主 Bundle 中加载资源。
4.3 卸载流程
Provider 的卸载流程:
markdown
Provider 卸载流程
1. 接收卸载请求
2. 减少引用计数
3. 引用计数为 0 时启动销毁
4. 释放资源
5. 销毁 Bundle 引用
6. 销毁 Provider
接收卸载请求详解:
用户调用 UnloadAsset 时,ResourceManager 减少 Provider 的引用计数。
引用计数减少详解:
每次卸载请求会减少 Provider 的引用计数。
销毁触发详解:
引用计数为 0 时,Provider 启动销毁流程。
4.4 卸载实现
csharp
public class AssetProvider : ProviderBase
{
public override void UnProvide()
{
DecreaseRefCount();
if (RefCount > 0) return;
Status = EProviderStatus.Destroyed;
// 释放资源
if (AssetObject != null)
{
if (AssetObject is GameObject)
{
// 释放 GameObject 资源
}
Resources.UnloadAsset(AssetObject);
}
// 减少 Bundle 引用
Package.ResourceManager.UnloadBundle(_bundleLoader);
foreach (var loader in _dependLoaders)
{
Package.ResourceManager.UnloadBundle(loader);
}
}
}
资源释放详解:
使用 Resources.UnloadAsset 释放资源。
Bundle 引用减少详解:
减少 Bundle 的引用计数,引用为 0 时销毁 Bundle。
五、Provider 类型
5.1 AssetProvider
AssetProvider 是最常用的 Provider,用于加载普通资源:
csharp
public class AssetProvider : ProviderBase
{
public override void Provide();
public override void UnProvide();
public override void Update();
}
AssetProvider 的用途详解:
加载 Prefab、Texture、Material、AudioClip 等普通资源。
AssetProvider 的实现详解:
AssetProvider 继承自 ProviderBase,实现了 Provide、UnProvide、Update 方法。
5.2 SceneProvider
SceneProvider 用于加载场景资源:
csharp
public class SceneProvider : ProviderBase
{
public Scene Scene { get; }
public AsyncOperation LoadSceneAsync();
public AsyncOperation UnloadSceneAsync();
}
SceneProvider 的用途详解:
加载 Unity 场景。
SceneProvider 的特殊处理详解:
SceneProvider 使用 Unity 的 SceneManager 加载场景,并管理场景的激活和卸载。
5.3 ComponentProvider
ComponentProvider 用于加载 Component 类型的资源:
csharp
public class ComponentProvider : ProviderBase
{
public GameObject Instance { get; }
public T GetComponent<T>() where T : Component;
}
ComponentProvider 的用途详解:
加载 Prefab 并获取其中的 Component。
ComponentProvider 的实现详解:
ComponentProvider 在 AssetProvider 的基础上,提供了 Component 获取的能力。
5.4 SubAssetsProvider
SubAssetsProvider 用于加载子资源:
csharp
public class SubAssetsProvider : ProviderBase
{
public Object[] SubAssets { get; }
}
SubAssetsProvider 的用途详解:
加载包含多个资源的 AssetBundle 中的特定子资源。
SubAssetsProvider 的应用场景详解:
加载 Sprite Atlas 中的特定 Sprite、加载 AnimationClip 等。
5.5 RawFileProvider
RawFileProvider 用于加载原生文件:
csharp
public class RawFileProvider : ProviderBase
{
public byte[] RawData { get; }
public string RawText { get; }
}
RawFileProvider 的用途详解:
加载原生格式的文件,如图片、文本等。
RawFileProvider 的实现详解:
RawFileProvider 不会将资源转换为 Unity 对象,而是返回原始的字节数据。
5.6 BundleProvider
BundleProvider 用于加载整个 Bundle:
csharp
public class BundleProvider : ProviderBase
{
public AssetBundle AssetBundle { get; }
}
BundleProvider 的用途详解:
加载整个 AssetBundle,不加载具体的资源。
BundleProvider 的应用场景详解:
需要手动管理 AssetBundle 内容的场景。
六、Provider 缓存机制
6.1 缓存的作用
Provider 缓存的作用:
避免重复创建:
相同资源只创建一次 Provider,避免重复创建的开销。
复用 Provider:
相同资源的加载请求复用同一个 Provider,避免重复加载。
提高性能:
通过缓存减少内存分配和资源加载。
6.2 缓存的实现
csharp
public class ResourceManager
{
private Dictionary<string, ProviderBase> _providerCache = new Dictionary<string, ProviderBase>();
public ProviderBase GetProvider(string location, Type assetType)
{
string key = GenerateProviderKey(location, assetType);
if (_providerCache.TryGetValue(key, out var provider))
{
return provider;
}
return null;
}
public void CacheProvider(ProviderBase provider)
{
string key = GenerateProviderKey(provider.Location, provider.AssetType);
_providerCache[key] = provider;
}
}
缓存 Key 生成详解:
缓存 Key 由 location 和 assetType 组成,确保相同资源使用相同的 Key。
缓存查询详解:
加载资源时先查询缓存,命中则复用,未命中则创建。
6.3 缓存的清理
csharp
public class ResourceManager
{
public void ClearUnusedProviders()
{
var unused = _providerCache.Where(p => p.Value.RefCount == 0).ToList();
foreach (var kvp in unused)
{
kvp.Value.Destroy();
_providerCache.Remove(kvp.Key);
}
}
}
清理条件详解:
引用计数为 0 的 Provider 是未使用的 Provider。
清理时机详解:
可以在资源切换、关卡切换等时机调用清理方法。
七、总结
本章深入解析了 YooAsset 资源提供者(Provider 体系)的源码实现,包括:
- Provider 概述:Provider 的概念、状态机、状态详解
- Provider 创建/获取:Provider 的创建、获取、销毁
- Provider 加载/卸载:加载流程、加载实现、卸载流程、卸载实现
- Provider 类型:AssetProvider、SceneProvider、ComponentProvider、SubAssetsProvider、RawFileProvider、BundleProvider
- Provider 缓存机制:缓存的作用、实现、清理
Provider 体系是 YooAsset 资源加载的核心,通过深入理解 Provider 体系,开发者可以更好地使用 YooAsset 的资源加载功能,并实现自定义的 Provider。
上一篇 :寻址系统 下一篇 :异步体系(OperationSystem)