Unity加载AB包(AssetBundle加载管理器,加载多个AB包,依赖包,管理AB的卸载)

最近刚学习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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值