最近刚学习Unity,通常将资源打包到AssetBundle中,但在加载的时候,常遇到一些问题。在网上并没找到一个好用的AB加载管理器,决定自己动手写一个。
AB包加载管理器要实现的功能:
1.依赖包的加载,AB包可能依赖别的AB包
加载顺序为:
1)加载MainAB包
2)mainfest文件
3)读取目标所依赖的依赖包
4)加载依赖AB包
5)加载目标AB包。
2.同时加载多个Ab包的冲突问题
因为同一个AB包只能加载一次(不然会报错)
3.可设置并发加载数量
不论加载多少个包,同时只会有n个加载任务在工作(n可设置)。
4.Ab包的释放
对于不再使用的AB包,可以Unload掉。同时Unload掉它的依赖包。但是这个依赖包也可能被别的AB包所依赖。
我的解决方法是:
1)需要建立一个引用计数(每一个独立的AB包)
2)每次加载一个依赖包,就将目标AB包及其依赖的引用计数 +1。
3)每次释放依赖包,就将目标包及其依赖包的引用计数 -1
4)当某个Ab包的引用计数 =0时,可以 n 秒后,Unload此Ab包。(n可设置)
5)当n秒内,计划要Unload掉的Ab包,如果再次被引用,那么该AB包不会被 Unload掉。
AB包加载管理器代码
1.ABManager代码
using System.Collections;
using System.Collections.Generic;
using RSG;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
public class ABManager : BaseSingletonMono<ABManager>
{
//同时并发的加载任务数,每一次使用者调用加载方法,就生成了一个任务
private static readonly int MAX_TASK = 5;
//同时并发的加载器数量,如=0,那就是同时有10个文件在加载
private static readonly int MAX_WORKERS = 10;
//保存已经加载完的AB包
private Dictionary<string, AssetBundle> _dicAB = new Dictionary<string, AssetBundle>();
//存放每个Ab包的引用数量
private Dictionary<string, int> _dicRef = new Dictionary<string, int>();
//保存AbName对应的销毁Promise
private Dictionary<string, IPromise> _dicUnloadPromise = new Dictionary<string, IPromise>();
//保存等待中的任务,等待 被启动
private List<ABTask> listTaskWating = new List<ABTask>();
//保存正在加载中的任务
private ABTask[] arrayTaskLoading = new ABTask[MAX_TASK];
//已经加载完的Task放在这里面Hold住,当用户Release AB的时候 ,才从这里面去掉。
private List<ABTask> listTaskHold = new List<ABTask>();
//保存正在工作的下载器,其只负责加载工作,不负责加载去重,给什么就加载什么
private ABLoadWorker[] arrayLoadWorker = new ABLoadWorker[MAX_WORKERS];
//存放主包
private AssetBundle mainAB = null;
//主包配置信息
private AssetBundleManifest manifest = null;
private PromiseTimer promiseTime = new PromiseTimer();
//当引用计数为0时,会在_TimeToUnload(秒)后Unload掉
private int _TimeToUnload = 30;
//当引用计数为0时,会在_TimeToUnload(秒)后Unload掉
public int TimeToUnload
{
get { return _TimeToUnload; }
//最少为1秒。
set { _TimeToUnload = Mathf.Max(1, value); }
}
enum MainABState { Unload, Loading, Loaded }
/// <summary>
/// -1:未开始 0:进行中,1:已经完成
/// </summary>
private MainABState IsMainABLoaded { get; set; }
private void Awake()
{
IsMainABLoaded = MainABState.Unload;
}
#region 外部方法
/// <summary>
/// 检查某个AB包是否已经加载
/// </summary>
/// <param name="abName">包名</param>
/// <returns></returns>
public bool Contains(string abName)
{
abName = abName.ToLower();
return _dicAB.ContainsKey(abName);
}
/// <summary>
/// /异步加载AB包
/// </summary>
/// <param name="abName"></param>
/// <returns></returns>
public Promise LoadABAsync(string abName)
{
abName = abName.ToLower();
Promise p = new();
ABTask task = new ABTask(abName, p);
_SetDependent(task);
listTaskWating.Add(task);
return p;
}
/// <summary>
/// 异步加载AB包中的资源
/// </summary>
/// <typeparam name="T">资源类型</typeparam>
/// <param name="abName">包名</param>
/// <param name="resName">资源名</param>
/// <returns></returns>
public Promise<T> LoadResAsync<T>(string abName, string resName) where T : Object
{
abName = abName.ToLower();
Promise<T> p = new();
if (Contains(abName))
{
//如果包已经加载过,直接读取资源
_LoadResFromAB(_dicAB[abName], resName, p);
}
else
{
Debug.LogError("请确包AssettBundle已经加载再调用 LoadResAsync");
/*
这里注释掉,确定只能先LoadABAsync后再LoadResAsync,
主要目标是为了引用计数,可以更准确地释放AB包,节省内存
*/
// //先加载包,然后再读取资源
// LoadABAsync(abName).Then(() =>
// {
// _LoadResFromAB(_dicAB[abName], resName, p);
// });
}
return p;
}
/// <summary>
/// 释放对AB的引用:
/// 每次调用 LoadABAsync(),会增加一个对AB包以及其 依赖包的的引用。
/// 当调用ReleaseAB()时,会减少一个对AB包及其依赖包的引用。
/// 当某一个AB包的引用计数=0时,会默认在一定秒数后Unload掉。
/// </summary>
/// <param name="abName"></param>
public void ReleaseAB(string abName)
{
ABTask task;
bool found = false;
abName = abName.ToLower();
for (int i = 0; i < listTaskHold.Count; i++)
{
task = listTaskHold[i];
if (task.ABName == abName)
{
listTaskHold.RemoveAt(i);
found = true;
_ReleaseTaskRef(task);
break;
}
}
if (!found)
{
Debug.LogWarning("在已下载的任务中,未找到此AB包的任务,所以未进行清理动作 abName = " + abName);
Debug.LogWarning("此AB包有可能正在加载中,或者未开始加载 abName = " + abName);
}
}
#endregion
#region 内部方法
private void _ReleaseTaskRef(ABTask task)
{
if (IsMainABLoaded != MainABState.Loaded) return;
_ReleaseRef(task.ABName);
string[] dependents = manifest.GetAllDependencies(task.ABName);
foreach (string s in dependents)
{
_ReleaseRef(s);
}
}
private void _AddTaskRef(ABTask task)
{
if (IsMainABLoaded != MainABState.Loaded) return;
_addRef(task.ABName);
string[] dependents = manifest.GetAllDependencies(task.ABName);
foreach (string s in dependents)
{
_addRef(s);
}
}
private void _addRef(string abName)
{
if (IsMainABLoaded != MainABState.Loaded) return;
if (_dicRef.ContainsKey(abName))
{
_dicRef[abName]++;
}
else
{
_dicRef[abName] = 1;
}
if (_dicUnloadPromise.ContainsKey(abName))
{
IPromise p = _dicUnloadPromise[abName];
promiseTime.Cancel(p);
_dicUnloadPromise.Remove(abName);
}
}
private void _ReleaseRef(string abName)
{
if (manifest == null) return;
if (!_dicRef.ContainsKey(abName) || _dicRef[abName] == 0) return;
_dicRef[abName]--;
if (_dicRef[abName] == 0)
{
//启动回收计时
IPromise promiseUnload = promiseTime.WaitFor(TimeToUnload);
if (_dicUnloadPromise.ContainsKey(abName))
{
IPromise promiseOld = _dicUnloadPromise[abName];
promiseTime.Cancel(promiseOld);
}
_dicUnloadPromise[abName] = promiseUnload;
promiseUnload.Done(() =>
{
_DoUnload(abName);
});
}
}
private void _DoUnload(string abname)
{
if (_dicUnloadPromise.ContainsKey(abname))
{
_dicUnloadPromise.Remove(abname);
if (_dicRef.ContainsKey(abname) && _dicRef[abname] <= 0)
{
_dicRef.Remove(abname);
if (_dicAB.ContainsKey(abname))
{
AssetBundle ab = _dicAB[abname];
Debug.Log($"------卸载AB:{abname}");
ab.Unload(false);
_dicAB.Remove(abname);
}
}
}
}
private void Update()
{
#region 处理MainAb逻辑
//maianAB未加载,启动加载
if (IsMainABLoaded == MainABState.Unload)
{
IsMainABLoaded = MainABState.Loading;
_LoadMainAB();
return;
}
//如果MainAB加载中,继续等待
if (IsMainABLoaded == MainABState.Loading) return;
#endregion
#region 处理Task逻辑
//将等待中的Task加入加载列表
if (listTaskWating.Count > 0)
{
for (int i = 0; i < MAX_TASK; i++)
{
if (arrayTaskLoading[i] == null)
{
ABTask task = listTaskWating[0];
arrayTaskLoading[i] = task;
task.StartTask();
_AddTaskRef(task);
//新加入列表的Task,刷新状态
task.UpdateState();
listTaskWating.RemoveAt(0);
break;//每一帧只添加一个任务进列表
}
}
}
int TaskNumInLoading = 0;
//将已经完成的Task移出
for (int i = 0; i < MAX_TASK; i++)
{
ABTask task = arrayTaskLoading[i];
if (task != null)
{
TaskNumInLoading++;
if (task.TaskState == ABTAskState.Complete)
{
listTaskHold.Add(arrayTaskLoading[i]);//已经加载完的Task,Hold住
arrayTaskLoading[i] = null;
TaskNumInLoading--;
}
}
}
#endregion
#region 处理Worker逻辑
if (TaskNumInLoading > 0)
{
ABLoadWorker worker;
for (int i = 0; i < MAX_WORKERS; i++)
{
worker = arrayLoadWorker[i];
if (worker == null)
{//发现空闲的worker
string abNameToload = _GetABNameFromTaskLoading();
int workerIndex = i;
if (abNameToload != null)
{
ABLoadWorker abLoadWorker = new ABLoadWorker();
arrayLoadWorker[workerIndex] = abLoadWorker;
abLoadWorker.StartLoad(abNameToload).Then((assetBundle) =>
{
arrayLoadWorker[workerIndex] = null;
_dicAB[abNameToload] = assetBundle;
//更新任务状态
_UpdateStateOfLoadingTasks();
});
}
break;//每一帧 只添加一个新Task
}
}
}
promiseTime.Update(Time.deltaTime);
#endregion
}
#region 加载资源相关
/// <summary>
/// 从AB包中加载资源,启动协程
/// </summary>
private void _LoadResFromAB<T>(AssetBundle ab, string resName, Promise<T> promise) where T : Object
{
StartCoroutine(_RealyLoadResFromAB<T>(ab, resName, promise));
}
/// <summary>
/// 从AB包中加载资源 协程函数
/// </summary>
private IEnumerator _RealyLoadResFromAB<T>(AssetBundle ab, string resName, Promise<T> promise) where T : Object
{
AssetBundleRequest abr = ab.LoadAssetAsync<T>(resName);
yield return abr;
promise.Resolve(abr.asset as T);
}
#endregion
/// <summary>
/// 刷新所有加载列表中的状态,一般在新的AB加载完成时调用
/// </summary>
private void _UpdateStateOfLoadingTasks()
{
for (int i = 0; i < MAX_TASK; i++)
{
ABTask task = arrayTaskLoading[i];
if (task != null)
{
task.UpdateState();
}
}
}
//轮训,记录上次轮训到哪个task
private int _PrevFatchTaskIndex = MAX_TASK - 1;
/// <summary>
/// 从进行中的任务中取得一个可用的AB
/// 为了确保每个Task能均等得到下载机会
/// 采用轮训的形式
/// </summary>
/// <returns></returns>
private string _GetABNameFromTaskLoading()
{
int index = _PrevFatchTaskIndex;
for (int i = 0; i < MAX_TASK; i++)
{
index++;
if (index >= MAX_TASK) index = 0;
if (arrayTaskLoading[index] != null)
{
string abName = arrayTaskLoading[index].GetABNameNeedLoad();
//如果资源未加载,并且并没有在加载中,那就是个合格的加载目标
if (!_CheckAbNameIsLoading(abName) && !_dicAB.ContainsKey(abName))
{
_PrevFatchTaskIndex = index;
return abName;
}
}
}
return null;
}
/// <summary>
/// 判断某个ab是否在加载中
/// </summary>
/// <param name="abName"></param>
/// <returns></returns>
private bool _CheckAbNameIsLoading(string abName)
{
for (int i = 0; i < MAX_WORKERS; i++)
{
ABLoadWorker worker = arrayLoadWorker[i];
if (worker != null && worker.ABName == abName)
{
return true;
}
}
return false;
}
/// <summary>
/// 加载主包
/// </summary>
private void _LoadMainAB()
{
ABLoadWorker.Create(MainABName).Then(ab =>
{
mainAB = ab;
_dicAB[MainABName] = ab;
manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
IsMainABLoaded = MainABState.Loaded;
//为所有等待中的Task设置Task信息
foreach (ABTask task in listTaskWating)
{
_SetDependent(task);
}
});
}
/// <summary>
/// 为Task增加依赖包信息
/// 有两个时机:
/// 1.新增任务时,如果主包已经加载完,会直接设置依赖包信息
/// 2.在主包刚加载完时,会将等待中的Task添加依赖包信息
/// </summary>
private void _SetDependent(ABTask abTask)
{
if (IsMainABLoaded == MainABState.Loaded)
{
string[] strs = manifest.GetAllDependencies(abTask.ABName);
abTask.SetDependent(strs);
}
}
/// <summary>
/// 针对不同的平台,主包名不同
/// </summary>
private string MainABName
{
get
{
#if UNITY_IOS
return "IOS";
#elif UNITY_ANDROID
return "Android";
#elif UNITY_EDITOR_WIN
return "StandaloneWindows";
#else
return "StandaloneOSXUniversal";
#endif
}
}
#endregion
}
#region 辅助类 ABTAskState
internal enum ABTAskState
{
PreInit, //未初始化,即未为其设置依赖包信息
Inited, //已经初始化
Loading, //正为加载中
Complete //已经加载完成
}
/// <summary>
/// 一个任务,就是一次外部调用
/// 它包含了一个主包名,以及它所依赖的AB包的列表
/// 同时包含一个Promise,当任务完成的时候,完成这个Promise
/// </summary>
internal class ABTask
{
//包名
public string ABName { get; private set; }
//Promise
public Promise PromiseAB { get; private set; }
//任务状态
public ABTAskState TaskState { get; private set; }
//依赖包列表,会随着加载过程完成,逐步减少
private List<string> ListABDependent;
public ABTask(string abName, Promise p)
{
this.ABName = abName;
this.PromiseAB = p;
TaskState = ABTAskState.PreInit;
}
/// <summary>
/// 设置依赖包信息
/// </summary>
public void SetDependent(string[] dependents)
{
if (TaskState != ABTAskState.PreInit)
{
Debug.LogError("TaskState Error : Not PreInit");
return;
}
TaskState = ABTAskState.Inited;
ListABDependent = new List<string>();
if (dependents != null && dependents.Length > 0)
{
for (int i = 0; i < dependents.Length; i++)
{
//只保留未加载的AB依赖包
if (!ABManager.Instance.Contains(dependents[i]))
{
ListABDependent.Add(dependents[i]);
}
}
}
}
/// <summary>
/// 开始加载该Task
/// </summary>
public void StartTask()
{
if (TaskState != ABTAskState.Inited)
{
Debug.LogError("TaskState Error : Not Inted");
return;
}
//启动加载
TaskState = ABTAskState.Loading;
}
/// <summary>
/// 刷新状态 ,一般在加载完一个新AB的时候调用
/// </summary>
public void UpdateState()
{
if(TaskState === ABTAskState.Complete) return;
if (TaskState != ABTAskState.Loading)
{
Debug.LogError("TaskState Error : Not Loading");
return;
}
for (int i = ListABDependent.Count - 1; i >= 0; i--)
{
string abDependent = ListABDependent[i];
if (ABManager.Instance.Contains(abDependent))
{
ListABDependent.RemoveAt(i);
}
}
if (ListABDependent.Count == 0)
{
if (ABManager.Instance.Contains(ABName))
{
PromiseAB.Resolve();
TaskState = ABTAskState.Complete;
}
}
}
/// <summary>
/// 对外提供一个ABName以便加载。
/// 优先取依赖包,当依赖包加完,才提供目标包。
/// 每次都轮训以后,都将第一个放在最后,以避免每次都提供同一个,
/// 这样可以让后面的依赖包更快地得到加载。
/// </summary>
/// <returns></returns>
public string GetABNameNeedLoad()
{
if (ListABDependent.Count > 0)
{
string x = ListABDependent[0];
//将第一个放在最后,机智 Boy
ListABDependent.RemoveAt(0);
ListABDependent.Add(x);
return x;
}
else
{
return ABName;
}
}
}
#endregion
#region 辅助类 AbLoadWorker
internal class ABLoadWorker
{
public static Promise<AssetBundle> Create(string abName)
{
// Promise<AssetBundle> promise = new Promise<AssetBundle>();
ABLoadWorker abLoadWorker = new ABLoadWorker();
return abLoadWorker.StartLoad(abName);
// return promise;
}
//资源包url
public string ABName { get; private set; }
private Promise<AssetBundle> _PromiseAB;
/// <summary>
/// 开启加载
/// </summary>
public Promise<AssetBundle> StartLoad(string abName)
{
_PromiseAB = new Promise<AssetBundle>();
ABName = abName;
Debug.Log("--------ABLoaderWorkers: Start load ab: " + abName);
ABManager.Instance.StartCoroutine(_RealyLoad());
return _PromiseAB;
}
/// <summary>
/// 加载AB包 协程函数
/// 开发环境下,从本地加载
/// WEBGL下,从WEB下载
/// </summary>
/// <returns></returns>
public IEnumerator _RealyLoad()
{
#if !UNITY_EDITOR
using(UnityWebRequest uwr = UnityWebRequestAssetBundle.GetAssetBundle(GetABUrl(ABName)))
{
yield return uwr.SendWebRequest();
if(uwr.result != UnityWebRequest.Result.Success){
Debug.LogError($"UnityWebRequest Error: {uwr.error}");
}else{
AssetBundle ab = DownloadHandlerAssetBundle.GetContent(uwr);
_PromiseAB?.Resolve(ab);
}
}
#else
AssetBundleCreateRequest abcrABMain = AssetBundle.LoadFromFileAsync(GetABUrl(ABName));
yield return abcrABMain;
AssetBundle assetBundle = abcrABMain.assetBundle;
_PromiseAB?.Resolve(assetBundle);
#endif
}
private string PathUrl
{
get
{
return Application.streamingAssetsPath + "/";
}
}
public string GetABUrl(string abName)
{
return PathUrl + abName;
}
}
#endregion
2.父类单例模式代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 继承MonoBehavior的单例基类
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class BaseSingletonMono<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instatnce;
public static T Instance
{
get
{
if (instatnce == null)
{
//动态挂载
GameObject obj = new GameObject();
instatnce = obj.AddComponent<T>();
obj.name = typeof(T).ToString();
//过场景的时候不移除,保证在整个生命周基都存在
DontDestroyOnLoad(obj);
}
return instatnce;
}
}
}
3.Promise参考:
https://zhuanlan.zhihu.com/p/34053986
ABManager的使用方法
1.加载AB包
ABManager.Instance.LoadABAsync("abName")
2.在AB已经加载后,加载包内资源
ABManager.Instance.LoadResAsync<GameObject>("abName","resName")
.Then(a=>{
GameObject g = Instantiate(a,transform);
});
3.在Ab包未加载前,加载包内资源
ABManager.Instance.LoadABAsync("abName")
.Then(()=>ABManager.Instance.LoadResAsync<GameObject>("abName","resName"))
.Done( a=>{
GameObject gameObject = Instantiate(a,transform);
});
4.卸载AB包
ABManager.Instance.ReleaseAB("abName");
5.设置Unload缓冲时间
//30秒后,Unlaod不用的资源,释放内存。30秒内再次被引用,将停止释放
ABManager.Instance.TimeToUnload = 30;