unity游戏框架学习-资源加载模块

概述:https://www.cnblogs.com/wang-jin-fu/p/10975660.html

本篇我们实现unity里的加载模块,他的主要功能是,业务传入资源名字和资源类型,加载模块加载到对应的资源后返回给业务,业务不需要关心该资源是从本地加载还是从AssetBundle里加载。

加载模块分两部分1.各资源的加载器,例如ab包加载器、Asset加载器、网络下载。2.各加载器的管理类,提供给业务的接口都在这里

需要支持的能力

1.能切换不同加载模式 开发阶段编辑器运行直接加载资源无需打ab包,测试或正式发布阶段通过ab包加载资源

2.缓存机制 能定时清理长时间未使用的资源内存

3.既有同步加载 也有异步加载

复杂点:

1.根据业务传入的资源名字,获取到editor路径、ab包名字。需要事先根据资源名字保存资源的路径、ab包路径配置。

2.ab包的引用计数维护:加载时ReferencedCount+1,卸载时ReferencedCount-1。

两种引用:AB包之间的相互依赖,ab包加载时,依赖包引用计数加1,ab包卸载时,依赖包引用减1。2.资源引用,例如使用AssetBundle.LoadAsset加载资源时,该ab包引用计数加一,引用对象被删除时,引用计数减1.

问题是如何确保被删除的引用对象引用计数能正确减少。

有两种方式:

  1.纯引用计数。ab包依赖和asset引用都使用引用计数。asset引用类型大概以下几种

  1-1.预制体,额外封装一层,所有预制体的生成和销毁都由一个管理类统一管理。

例如封装一个ResourceItem类,所有预制体的生成和销毁都必须走这个类的的Create和Dispose类,在ctor方法里加载ab包、实例化预制体,在Dispose方法里Distory对象、卸载ab包(这里的加、卸载只是引用计数加1、减1)。需要业务手动的释放调用Dispose释放对象。

-- 资源
-- 所有非UI的预制加载
local ResourceItem = class("ResourceItem", ObjectBase)

-- 静态创建ResourceItem接口
-- path Data目录以下,预制的路径
function ResourceItem.Create(target, filepath, parent, onLoaded, async)
    local abpath = PubFunc.GetAbNameOfPath(filepath)
    local name =  PubFunc.GetNameFromPath(filepath)
    local item = ResourceItem.new(filepath, abpath, name, parent, onLoaded, async)
    target:AddSubItem(item)
    return item
end

function ResourceItem.CreateUIItem(target, abpath, name, parent, onLoaded, async)
    local filepath = abpath
    if string.find(abpath, name)==nil then
        filepath = abpath..name
    end

    local item = ResourceItem.new(filepath, abpath, name, parent, onLoaded, async)
    target:AddSubItem(item)
    return item
end

function ResourceItem:ctor(filepath, abpath, name, parent, onLoaded, async)
    ResourceItem.super.ctor(self)async = async and true or false

    self._filepath  = filepath
    self._path         = abpath         -- 文件路径
    self._name         = name
    self._parent    = parent
    self._onLoaded     = onLoaded     -- 加载完回调

    self.gameObject         = nil         --外部可直接获取
    self.transform             = nil

    self._loadKey = me.modules.resource:CreateAsyn(abpath, name, handler(self, self.OnLoadComplete), async)
end-- 清理
function ResourceItem:Dispose()
    if self.gameObject then
        -- 销毁
        me.modules.resource:Delete(self.gameObject)
        self.gameObject = nil
        self.transform = nil
    elseif self._loadKey then
        -- 取消加载
        me.modules.resource:CancelLoad(self._loadKey)
        self._loadKey = nil
    end

    ResourceItem.super.Dispose(self)
end-- 是否已加载
function ResourceItem:IsLoaded()
    return self.gameObject ~= nil
end

-- 加载完成回调
function ResourceItem:OnLoadComplete(go)
    self._loadKey    = nil
    local trans = nil

    if go then
        trans = go.transform
        if self._parent then
            go:SetParent(self._parent)
        end
        trans:SetLocalPositionZero()
        trans:SetLocalScaleOne()
    else
        printError("加载RedourceItem失败,path:", self._path)
    end
    self.gameObject = go
    self.transform = trans

    -- 回调给外部
    if self._onLoaded then
        self._onLoaded(self)
    end
end

return ResourceItem

你也可以在每个实例化的GameObject上挂在一个脚本,并在该脚本的Destory方法里卸载ab包的引用  

1-2.场景类,这个比较简单,场景管理类肯定会记录当前的场景信息,在加载新场景时,先卸载当前的ab包就可以了。

1-3.sprite类,sprite是给image使用的,那么我们可以扩展一下Image的类。例如业务传入图片的名字,ImageEx类根据名字到LoadModule加载对应的ab及sprite并记录当前的sprite名字,当业务下次设置图片或Image对象被Destory时,根据保存的sprite名字卸载ab包。  

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Debugger = LuaInterface.Debugger;

/// <summary>
/// image扩展,提供通过图片名字加载图片、从ab包、url、高清资源下载、emoji加载图片接口
/// TP形Image. 
/// </summary>
public class ImageEx : Image
{
    private string m_SpriteName = "";
    public string SpriteName
    {
        get
        {
            return m_SpriteName;
        }
        set
        {
            m_SpriteName = value;
        }
    }protected override void OnDestroy()
    {
        // 销毁的时候要卸载一下ab
        UnloadSprite();
        StopCurrLoadingUrl();
    }

    private void UnloadSprite()
    {
        if (!string.IsNullOrEmpty(SpriteName) && sprite != null)
        {
            SpriteModule.Instance.UnloadSpriteByName(SpriteName);
            SpriteName = null;
            this.sprite = null;
        }
    }public void SetSpriteName(string name)
    {
        if (sprite != null && SpriteName == name)
        {
            return;
        }

        UnloadSprite();
        
        if(string.IsNullOrEmpty(name))
        {
            this.sprite = null;
            return;
        }

        SpriteName = name;
        SpriteModule.Instance.LoadSpriteByName(name, onLoadedSprite);
    }private void onLoadedSprite(object obj)
    {
        Sprite sp = obj as Sprite;
        this.sprite = sp;
        if (sp == null)
        {
            Debugger.LogError("Load sprite null, name:{0}", m_SpriteUrl);
        }
        else
        {
            if(!string.IsNullOrEmpty(m_strHdResName))
            {
                sprite.name = m_strHdResName;
            }
        }
    }
}

  1-4.shader类:全局就一个ab包,常驻内存就好了

  1-5.音乐类:PlayMusic加载、StopMusic卸载就好了

// 背景音乐
    // fromResources 是否从Resources文件夹下加载 
    public void PlayMusic(string name, bool fromResources = false)
    {
        if (string.IsNullOrEmpty(name)) return;

        if (fromResources)
        {
            if(MusicMute)
            {
                return;
            }
            AudioClip clip = Resources.Load<AudioClip>(name);
            if(clip != null)
            {
                m_musicSource.enabled = true;
                m_musicSource.clip = clip;
                m_musicSource.loop = true;
                m_musicSource.Play();
            }
        }
        else
        {
            string strBundleName = "sound/music/" +name;
            LoadModule.Instance.LoadAssetFromBundle(strBundleName, name, typeof(AudioClip), (data) => {
                m_musicSource.enabled = true;
                m_musicSource.clip = data as AudioClip;
                m_musicSource.loop = true;
                m_musicSource.Play();
            });
        }
    }

    /// <summary>
    /// 停止音乐并清理
    /// </summary>
    public void StopMusic()
    {
        AudioClip m_musicClip = m_musicSource.clip;
        if (m_musicClip)
        {
            m_musicSource.Stop();
            m_musicSource.clip = null;

            string currentMusicName = m_musicClip.name;
            AssetBundleCache assetBundleCache = ABCachePool.Instance.GetABCacheByName(string.Format("sound_music_{0}.unity3d", currentMusicName));
            if (assetBundleCache != null)
            {
                assetBundleCache.ReferencedCount = 1;
                LoadModule.Instance.UnloadAssetBundle(string.Format("sound/music/{0}", currentMusicName), true);
            }
        }
    }  

  2.引用计数+弱引用。ab包依赖使用引用计数,asset引用使用弱引用,业务在加载asset时需要传入引用的对象(实例化就不用了,可以把实例化出来的GameObject当作引用对象),通过判断对象是否为空来判断引用关系。

  强引用:我们实例化一个对象,直接引用了这个对象就是强引用。在这个对象被强引用的时,GC无法回收这个对象。只有当该对象所有的强引用都失去的时候,GC才会回收该对象。

  弱引用:弱引用可以保持对对象的引用,同时允许GC在必要时释放对象,回收内存。这边一定要用弱引用,不然会影响对象的回收。

protected List<System.WeakReference> mReferenceOwnerList;
/// <summary>
/// 为AB添加指定owner的引用
/// 所有owner都销毁则ab引用计数归零可回收
/// </summary>
/// <param name="owner"></param>
protected void retainOwner(UnityEngine.Object owner)
{
   if (owner == null)
   {
       ResourceLogger.logErr(string.Format("引用对象不能为空!无法为资源:{0}添加引用!", AssetBundleName));
       return;
    }

    foreach (var referenceowner in mReferenceOwnerList)
    {
        if (owner.Equals(referenceowner))
        {
            return;
        }
    }

    System.WeakReference wr = new System.WeakReference(owner);
    mReferenceOwnerList.Add(wr);
}
/// <summary>
/// 获取AB有效的引用对象计数
/// </summary>
/// <returns></returns>
protected int updateOwnerReference()
{
   for (int i = 0; i < mReferenceOwnerList.Count; i++)
   {
        UnityEngine.Object o = (UnityEngine.Object)mReferenceOwnerList[i].Target;
        if (!o)
        {
            mReferenceOwnerList.RemoveAt(i);
            i--;
        }
    }
    return mReferenceOwnerList.Count;
}

第一种方式需要业务手动Dispose无用的对象,当然这是个好习惯,需要框架严格注意引用对象的管理。第二种需要业务在引用时传入引用的对象,需要额外的参数。

一个加载模块大致结构如下:

加载模块结构如上图,load为加载器,ResManager为提供给业务调用的接口,LoadModule为不使用ab包的加载接口,ABLoadModule为使用ab包的加载接口,这两个module对用户封闭。

AssetLoader:在editor模式下加载资源。AssetBundleLoader :ab包加载器,负责从内存加载AssetBundle。BundleAssetLoader :负责从指定的ab包加载资源。AssetBundleCache:缓存的ab包。

一、加载器实现

上篇我们有说到,unity有四种加载方式

1.AssetDatabase:在编辑器内加载卸载资源,并不能在游戏发布时使用,它只能在编辑器内使用。但是,它加载速度快,使用简单。

2.Resources:因为使用Resources文件夹无法热更,本片篇就不实现此途径了。

3.AssetBundle:参考https://www.cnblogs.com/wang-jin-fu/p/11171626.html,支持热更,但是每次资源变化都得重新打ab包(奇慢),所以适合发布模式,但开发模式千万别用。

4.UnityWebRequest:从网络端下载

1.所有的加载器都继承自一个接口:Loader,该类定义了当前的加载类型、初始化、回收的重置方法、加载方法、加载进度回调等

public class Loader
{
    #region Define
    public enum LoaderType
    {
        STREAM,            // 流(原则上可以是任何文件,包括远程服务器上的)
        ASSET,            // Asset目录下的资源
        BUNDLE,            // AssetBundle
        BUNDLEASSET,    // AssetBundle中的资源
        SCENE,          // 场景
        Texture,        // 图片
    }

    public enum LoaderState
    {
        NONE,            // 
        LOADING,        // 加载中
        FINISHED,        // 完成
    }

    public delegate void ProgressHandle(Loader loader, float rate);
    public delegate void LoadedHandle(Loader loader, object data);
    #endregion

    protected Loader(LoaderType type)
    {
        m_type = type;
    }

    protected LoaderType m_type;            // 加载器类型
    protected LoaderState m_state;            // 加载状态
    protected string m_path;                // 路径
    protected bool m_async;                    // 是否异步

    protected ProgressHandle m_onProgress;    // 加载进度
    protected LoadedCallback m_onloaded;    // 加载完成回调通知

    protected System.Diagnostics.Stopwatch m_watch = new System.Diagnostics.Stopwatch ();//加载时间统计

    public LoaderType Type { get { return m_type; } }
    public string Path { get { return m_path; } }
    public bool IsFinish { get { return m_state == LoaderState.FINISHED; } }
    public bool IsAsync { get { return m_async; } }

  //主要用于ab包的判断,因为ab包需要等待依赖包的加载
public virtual bool IsPrepareToLoad() { return true; } //初始化参数 public virtual void Init(string path, LoadedCallback onloaded, bool async = true) { m_state = LoaderState.NONE; m_path = path; m_async = async; m_onloaded = onloaded; } public virtual void Reset() { m_path = ""; m_async = true; m_onloaded = null; m_state = LoaderState.NONE; m_onProgress = null; } public virtual void Load() { m_watch.Reset (); m_watch.Start (); m_state = LoaderState.LOADING; OnLoadProgress(0f); } public virtual void Stop() { Reset(); } public virtual void Update(float dt) { } protected virtual void OnLoadProgress(float rate) { if (m_onProgress != null) { m_onProgress(this, rate); } } protected virtual void OnLoadCompleted(object data) { m_state = LoaderState.FINISHED; try { if (m_onloaded!= null) { m_onloaded (data); } } catch(System.Exception e) { LuaInterface.Debugger.LogError(e.Message); } OnLoadProgress(1f); } }

2.editor模式下的加载,直接使用AssetDatabase加载。

public class AssetLoader : Loader
{
    Object m_data = null;
    System.Type m_assetType = null;        //资源类型

    public AssetLoader()
        : base(Loader.LoaderType.ASSET)
    {

    }

    public void Init (string path, System.Type type, LoadedCallback onLoaded, bool async = true)
    {
        base.Init (path, onLoaded, async);
        m_assetType = type;
    }

    public override void Load()
    {
        base.Load();

#if UNITY_EDITOR
        if (m_assetType == null)
        {
            m_assetType = typeof(Object);
        }

        m_data = UnityEditor.AssetDatabase.LoadAssetAtPath(m_path, m_assetType);
        if (!m_async)
        {
            OnLoadCompleted(m_data);
        }
#else
        if(!m_async)
        {
            OnLoadCompleted(null);
        }
#endif
    }

    public override void Update(float dt)
    {
        if (m_state == LoaderState.LOADING)
        {
            OnLoadCompleted(m_data);
            m_data = null;
        }
    }
}

3.ab包的缓存可以参考我之前的文章:ab包的缓存

ab加载如下:当且仅当IsPrepareToLoad判断通过(即所有依赖包都加载完成)才能调用load方法,开始ab包的加载。InitDependencies方法用于初始化当前ab包的依赖项

load加载分两种,第一种是从扩展包加载,第二种是从本地加载。

ab包的加载无非就是同步和异步加载的区别,ab包的卸载也只需要调用Unload方法就好了。唯一需要注意的是,记载asset前必须保证ab的依赖包都加载完成了。

AssetBundleCache:缓存类,用于缓存ab包,提供从ab包加载asset的方法并缓存a包

public class AssetBundleCache
{
    private string m_name;          // AssetBundle name
    private int m_referencedCount;  // 引用计数
    private float m_unloadTime;     // 释放时间

    private HashSet<string> m_setAllAssetNames = null;//ab包包含的所有asset的名字,用于判断指定asset是否存在于ab中
    private Dictionary<string, AssetBundleRequest> m_dicAsync = new Dictionary<string, AssetBundleRequest>();//正在异步加载的asset
    private Dictionary<string, Object> m_dicAssetCache = null;//已经加载完的asset

    public AssetBundleCache(string name, AssetBundle ab, int refCount)
    {
        m_name = name;
        Bundle = ab;
        ReferencedCount = refCount;
    }

    // AssetBundle
    public AssetBundle Bundle
    {
        get;
        private set;
    }

    // 是否常驻,通用资源的ab包不卸载
    public bool Persistent
    {
        get;
        set;
    }

    public string BundleName
    {
        get
        {
            return m_name;
        }
    }

    // 引用计数
    public int ReferencedCount
    {
        get
        {
            return m_referencedCount;
        }

        set
        {
            m_referencedCount = value;
            if (m_referencedCount <= 0)
            {
                m_unloadTime = Time.realtimeSinceStartup;
            }
            else
            {
                m_unloadTime = 0;
            }
            if (m_referencedCount < 0)
            {
                Debug.LogWarningFormat("AssetBundleCache reference count < 0, name:{0}, referencecount:{1}", m_name, m_referencedCount);
            }
        }
    }

    // 是否可以删除
    public bool IsCanRemove
    {
        get
        {
            // 常驻资源
            if (Persistent) return false;

            // 非常驻,并且引用计数为0
            if (!Persistent && ReferencedCount <= 0)
            {
                return true;
            }

            return false;
        }
    }

    // 缓存时间到
    public bool IsTimeOut
    {
        get
        {
            return Time.realtimeSinceStartup - m_unloadTime >= Config.Instance.AssetCacheTime;
        }
    }

    // 资源是否正在异步加载中
    public bool IsAssetLoading(string name)
    {
        return m_dicAsync.ContainsKey(name);
    }

    //判断AB是否包含某个资源
    public bool IsExistAsset(string strAssetName)
    {
        if (m_setAllAssetNames != null && m_setAllAssetNames.Contains(strAssetName))
        {
            return true;
        }
        return false;
    }

    // 获取缓存中的资源
    public Object GetCacheAsset(string name)
    {
        if (m_dicAssetCache != null)
        {
            Object rst = null;
            if (m_dicAssetCache.TryGetValue(name, out rst))
            {
                return rst;
            }
        }
        return null;
    }

    // 资源加载完 要缓存一下
    public void OnLoadedAsset(string name, Object asset)
    {
        m_unloadTime = Time.realtimeSinceStartup;

        if (m_dicAsync.ContainsKey(name))
        {
            m_dicAsync.Remove(name);
        }

        // 常驻ab加载进来的资源 用真实引用 不用弱引用
        if (m_dicAssetCache == null)
        {
            m_dicAssetCache = new Dictionary<string, Object>();
        }
        if (m_dicAssetCache.ContainsKey(name))
        {
            Debug.LogWarningFormat("警报! 缓存已经存在了还重新加载:{0}", name);
        }
        m_dicAssetCache[name] = asset;
        return;
    }

    //异步加载资源,需要添加到m_dicAsync里,防止重复加载
    public AssetBundleRequest LoadAssetAsync(string name, System.Type type)
    {
        if (Bundle == null)
        {
            Debug.LogWarningFormat("AssetBundle:{0} is null, load asset async:{1},type:{2}, fail!!", m_name, name, type.ToString());
            return null;
        }
        AssetBundleRequest opt;
        m_dicAsync.TryGetValue(name, out opt);
        if (opt == null)
        {
            opt = Bundle.LoadAssetAsync(name, type);
            m_dicAsync.Add(name, opt);
        }
        return opt;
    }

    //加载资源
    public Object LoadAsset(string name, System.Type type)
    {
        if (Bundle == null)
        {
            Debug.LogWarningFormat("AssetBundle:{0} is null, load asset:{1},type:{2}, fail!!", m_name, name, type.ToString());
            return null;
        }

        Object asset = Bundle.LoadAsset(name, type);
        if (asset == null)
        {
            Debug.LogWarningFormat("AssetBuncleCache.LoadAsset, asset not exist:{0}, {1}", m_name, name);
        }
        else
        {
            OnLoadedAsset(name, asset);
        }

        return asset;
    }

    //加载所有资源
    public UnityEngine.Object[] LoadAllAssets(bool bCache = true)
    {
        UnityEngine.Object[] allObjs = Bundle.LoadAllAssets();
        if (bCache)
        {
            for (int i = 0; i < allObjs.Length; i++)
            {
                Object obj = allObjs[i];
                OnLoadedAsset(obj.name, obj);
            }
        }

        return allObjs;
    }

    //异步加载所有资源 只用作预加载使用
    public AssetBundleRequest LoadAllAssetsAsync()
    {
        if (Bundle == null)
        {
            return null;
        }
        return Bundle.LoadAllAssetsAsync();
    }

    public bool LoadAllAssetNames()
    {
        if (Bundle == null)
        {
            return false;
        }
        string[] arrNames = Bundle.GetAllAssetNames();
        if (arrNames.Length == 0)
        {
            return false;
        }
        if (m_setAllAssetNames == null)
        {
            m_setAllAssetNames = new HashSet<string>();
        }

        for (int i = 0; i < arrNames.Length; i++)
        {
            string strName = System.IO.Path.GetFileNameWithoutExtension(arrNames[i]);
            m_setAllAssetNames.Add(strName);
        }
        return true;
    }

    //卸载ab包
    public void Unload()
    {
        if (m_dicAsync.Count > 0)
        {
            Debug.LogWarningFormat("[仅提醒]该Bundle还有资源在加载中,暂时不卸载:{0}", m_name);
            return;
        }

        if (Bundle != null)
        {

            Bundle.Unload(false);
            Bundle = null;
        }

        if (m_setAllAssetNames != null)
        {
            m_setAllAssetNames.Clear();
        }

        if (m_dicAssetCache != null)
        {
            m_dicAssetCache.Clear();
        }
    }
}

ABCachePool:负责管理ab包的引用计数、缓存、获取。

public class ABCachePool
{
    #region Instance
    private static ABCachePool m_Instance;
    public static ABCachePool Instance
    {
        get { return m_Instance ?? (m_Instance = new ABCachePool()); }
    }
    #endregion
    Dictionary<string, AssetBundleCache> m_AssetBundleCaches = new Dictionary<string, AssetBundleCache>();  // 缓存队列
    HashSet<string> m_persistentABs = new HashSet<string>();

    public Dictionary<string, AssetBundleCache> AssetBundleCaches
    {
        get
        {
            return m_AssetBundleCaches;
        }
    }

    public void ClearAllCache()
    {
        foreach (KeyValuePair<string, AssetBundleCache> keyval in m_AssetBundleCaches)
        {
            keyval.Value.Unload();
        }
        m_AssetBundleCaches.Clear();
    }

    public bool IsExistCache(string abName)
    {
        return m_AssetBundleCaches.ContainsKey(abName);
    }

    // 引用这个bundle
    public AssetBundleCache ReferenceCacheByName(string abName)
    {
        AssetBundleCache cache = null;
        m_AssetBundleCaches.TryGetValue(abName, out cache);
        if (cache != null)
        {
            ++cache.ReferencedCount;
        }
        return cache;
    }

    // 获取ABCache 不增加引用
    public AssetBundleCache GetABCacheByName(string abName)
    {
        AssetBundleCache cache = null;
        m_AssetBundleCaches.TryGetValue(abName, out cache);
        return cache;
    }

    public AssetBundleCache AddCache(string abName, AssetBundle bundle, int refCount)
    {
        if (m_AssetBundleCaches.ContainsKey(abName))
        {
            Debug.LogWarningFormat("AssetBundleCache already contains key:{0}, it will be cover by new value.", abName);
        }

        AssetBundleCache cache = new AssetBundleCache(abName, bundle, refCount);
        m_AssetBundleCaches[abName] = cache;

        if (m_persistentABs.Contains(abName))
        {
            cache.Persistent = true;
        }

        return cache;
    }

    // immediate 只有场景是立刻卸载
    public AssetBundleCache UnReferenceCache(string abName, bool immediate = false)
    {
        AssetBundleCache cache = null;
        if (!m_AssetBundleCaches.TryGetValue(abName, out cache))
        {
            return null;
        }

        if (cache.Persistent)
        {
            return null;
        }

        --cache.ReferencedCount;

        if (immediate && cache.IsCanRemove)
        {
            RemoveCache(abName);
        }

        return cache;
    }

    public void RemoveCache(string abName)
    {
        AssetBundleCache cache = m_AssetBundleCaches[abName];
        cache.Unload();
        m_AssetBundleCaches.Remove(abName);
    }

    private List<string> m_lstRm = new List<string>();
    // 清除无引用的AssetBundle缓存
    public void ClearNoneRefCache(bool mustTimeout)
    {
        foreach (KeyValuePair<string, AssetBundleCache> keyval in m_AssetBundleCaches)
        {
            AssetBundleCache item = keyval.Value;
            // 只清除引用计数为0的
            if (item.IsCanRemove && (!mustTimeout || item.IsTimeOut))
            {
                m_lstRm.Add(keyval.Key);
            }
        }

        for (int i = 0; i < m_lstRm.Count; i++)
        {
            RemoveCache(m_lstRm[i]);
        }
        m_lstRm.Clear();
    }

    /// <summary>
    /// 常驻ab包设置
    /// </summary>
    /// <param name="arrAB"></param>
    public void SetPersistentABs(string[] arrAB)
    {
        m_persistentABs.Clear();
        for (int i = 0; i < arrAB.Length; i++)
        {
            string strAB = arrAB[i];//FileHelper.GenBundlePath(arrAB[i]);
            m_persistentABs.Add(strAB);

            AssetBundleCache abCache;
            m_AssetBundleCaches.TryGetValue(strAB, out abCache);
            if (abCache != null)
            {
                abCache.Persistent = true;
            }
        }
    }
}

BundleLoader:用于加载AssetBundle

public class BundleLoader : Loader
{
    AssetBundleCreateRequest m_abRequest = null;

    private int m_iRefCount;                // 当前等待此加载的引用计数
    private List<string> m_lstDepAbNames = new List<string>();  //依赖的未加载Bundle名字列表

    private LoadedCallback m_onABLoaded;

    private string m_strBundleName;         // 相对路径
    public string BundleName { get { return m_strBundleName; } }

    System.Diagnostics.Stopwatch m_saveAbWatcher = new System.Diagnostics.Stopwatch();
    public BundleLoader()
        : base(Loader.LoaderType.BUNDLE)
    {

    }

    public void AddLoadedCallback(LoadedCallback onloaded)
    {
        m_onABLoaded += onloaded;
        m_iRefCount += 1;
    }

    public void AddReferenced()
    {
        m_iRefCount += 1;
    }

    public override void Reset()
    {
        base.Reset();

        m_iRefCount = 0;
        m_lstDepAbNames.Clear();
        m_strBundleName = "";

        m_abRequest = null;
        m_onABLoaded = null;
        m_lstDepAbNames.Clear();
    }

    // 判断是否所有依赖都已经加载
    public override bool IsPrepareToLoad()
    {
        for (int i = m_lstDepAbNames.Count - 1; i >= 0; i--)
        {
            string strABName = m_lstDepAbNames[i];
            if (ABCachePool.Instance.IsExistCache(strABName))
            {
                m_lstDepAbNames.RemoveAt(i);
            }
            else
            {
                break;
            }
        }
        return m_lstDepAbNames.Count == 0;
    }

    public void Init(string path, string strName, string[] dependencies, LoadedCallback onloaded, bool async = true)
    {
        // Bundle 比较特殊 不使用父类的回调
        base.Init(path, null, async);

        m_iRefCount = 1;
        m_strBundleName = strName;
        m_onABLoaded = onloaded;
        InitDependencies(dependencies);
    }

    private void InitDependencies(string[] dependencies)
    {
        if (dependencies == null || dependencies.Length == 0)
            return;
        for (int i = 0; i < dependencies.Length; i++)
        {
            string strName = dependencies[i];
            if (!ABCachePool.Instance.IsExistCache(strName))
            {
                m_lstDepAbNames.Add(strName);
            }
        }
    }

    public override void Load()
    {
        base.Load();

        if (m_async)
        {
            string path = FileHelper.GetAPKPath(m_path);//根据ab包的名字索引ab包的存储路劲
            m_abRequest = AssetBundle.LoadFromFileAsync(path);
        }
        else
        {
            AssetBundle ab = null;
            try
            {
                // 同步使用AssetBundle.LoadFromFile加载,速度最快
                if (ab == null)
                {
                    string path = FileHelper.GetAPKPath(m_path);//根据ab包的名字索引ab包的存储路劲
                    ab = AssetBundle.LoadFromFile(path);
                }
            }
            catch (System.Exception e)
            {
                Debug.LogError(e.Message);
            }
            finally
            {
                OnLoaded(ab);
            }
        }
    }

    public override void Update(float dt)
    {
        if (m_state == LoaderState.LOADING)
        {
            if (m_abRequest != null)
            {
                if (m_abRequest.isDone)
                {
                    OnLoaded(m_abRequest.assetBundle);
                }
                else
                {
                    DoProgress(m_abRequest.progress);
                }
            }           
        }
    }

    void DoProgress(float rate)
    {
    }

    void OnLoaded(AssetBundle ab)
    {
        AssetBundleCache cache = ABCachePool.Instance.AddCache(m_strBundleName, ab, m_iRefCount);
        OnLoadCompleted(ab);

        if (m_onABLoaded != null)
        {
            m_onABLoaded(cache);
        }
    }
}

二、缓存池,缓存加载器

public class LoaderPool
{
    #region Instance
    private static LoaderPool m_Instance;
    public static LoaderPool Instance
    {
        get { return m_Instance ?? (m_Instance = new LoaderPool()); }
    }
    #endregion

    List<Loader> m_bundleLoaderPool = new List<Loader>();       // BundleLoader的缓存
    List<Loader> m_assetLoaderPool = new List<Loader>();        // BundleAssetLoader的缓存

    public T GetLoader<T>() where T : Loader, new()
    {
        System.Type type = typeof(T);
        T loader = null;
        if (type == typeof(BundleLoader))
        {
            if (m_bundleLoaderPool.Count > 0)
            {
                loader = (T)m_bundleLoaderPool[0];
                m_bundleLoaderPool.RemoveAt(0);
                return loader;
            }
        }
        else if (type == typeof(BundleAssetLoader))
        {
            if (m_assetLoaderPool.Count > 0)
            {
                loader = (T)m_assetLoaderPool[0];
                m_assetLoaderPool.RemoveAt(0);
                return loader;
            }
        }

        loader = new T();

        return loader;
    }

    public void RecycleLoader(Loader loader)
    {

        if (loader.GetType() == typeof(BundleLoader))
        {
            loader.Reset();
            m_bundleLoaderPool.Add(loader);
        }
        else if (loader.GetType() == typeof(BundleAssetLoader))
        {
            loader.Reset();
            m_assetLoaderPool.Add(loader);
        }
        else
        {
            loader.Reset();
        }
    }
}

三、加载管理器LoadModule

1.ResManager :提供给业务的接口

public class ResManager
{
    #region Instance
    private static ResManager m_Instance;
    public static ResManager Instance
    {
        get
        {
            return m_Instance ?? (m_Instance = new ResManager());
        }
    }
    #endregion
    public int SyncCount = 6;                               // 同步加载并发数

    private bool m_isUseAssetBundle;
    private ILoadModule m_loadModule;

    public void Init(bool isUseAssetBundle)
    {
        m_isUseAssetBundle = isUseAssetBundle;
        if (isUseAssetBundle)
        {
            m_loadModule = new ABLoadModule();
        }
        else
        {
            m_loadModule = new LoadModule();
        }

        m_loadModule.Init(SyncCount);
    }

    public void UnInit()
    {
        m_loadModule.UnInit();
    }

    public void Update(float dt)
    {
        m_loadModule.Update(dt);
    }

    public void Clear()
    {
        ABCachePool.Instance.ClearNoneRefCache(false);

        Resources.UnloadUnusedAssets();
    }

    public void LoadPrefab(string strPath, string name, LoadedCallback onLoaded, bool async = true)
    {
        m_loadModule.LoadPrefab(strPath, name, onLoaded, async);
    }

    public void LoadMusic(string name, LoadedCallback onLoaded, bool async = true)
    {
        m_loadModule.LoadMusic(name, onLoaded, async);
    }

    public void LoadFont(string name, LoadedCallback onLoaded, bool async = true, bool inBundle = false)
    {
        m_loadModule.LoadFont(name, onLoaded, async, inBundle);
    }

    public void LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded)
    {
        m_loadModule.LoadScene(name, scenePath, isAdditive, onLoaded);
    }
}

2.两种加载器的接口类ILoadModule

public interface ILoadModule
{
    void Init(int syncCout);

    void UnInit();

    void Update(float dt);

    void LoadPrefab(string strPath, string name, LoadedCallback onLoaded, bool async = true);

    void LoadMusic(string name, LoadedCallback onLoaded, bool async = true);

    void LoadFont(string name, LoadedCallback onLoaded, bool async = true, bool inBundle = false);

    void LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded);
}

3.editor模式下的加载器

/// <summary>
/// 不使用ab加载资源(Editor模式下)
/// </summary>
public class LoadModule : ILoadModule
{
    public int SyncCount;                               // 同步加载并发数
    public HashSet<string> m_loadedBundleNames = new HashSet<string>();
    // 加载队列
    List<Loader> m_loadings = new List<Loader>();//正在加载
    List<Loader> m_loaderQueue = new List<Loader>();//等待加载

    float m_lastClear = 0;  // 上一次清除时间

    public void Init(int syncCout)
    {
        SyncCount = syncCout;
    }

    public void UnInit()
    {
        for (int i = 0; i < m_loadings.Count; i++)
        {
            m_loadings[i].Stop();
        }

        m_loadings.Clear();
        m_loaderQueue.Clear();
    }

    private List<int> m_lstRmTemp = new List<int>();
    public void Update(float dt)
    {
        for (int i = m_loadings.Count - 1; i >= 0; i--)
        {
            Loader loader = m_loadings[i];
            loader.Update(dt);
            if (loader.IsFinish)
            {
                m_loadings.RemoveAt(i);
                LoaderPool.Instance.RecycleLoader(loader);
            }
        }

        int remain = Mathf.Min(SyncCount - m_loadings.Count, m_loaderQueue.Count);
        for (int i = 0; i < m_loaderQueue.Count; i++)
        {
            Loader loader = m_loaderQueue[i];
            m_loadings.Add(loader);
            loader.Load();
            loader.Update(dt);
            m_lstRmTemp.Add(i);
            if (m_lstRmTemp.Count >= remain)
            {
                break;
            }
        }

        if (m_lstRmTemp.Count > 0)
        {
            for (int i = m_lstRmTemp.Count - 1; i >= 0; i--)
            {
                m_loaderQueue.RemoveAt(m_lstRmTemp[i]);
            }
            m_lstRmTemp.Clear();
        }
    }

    public void Clear()
    {
        Resources.UnloadUnusedAssets();
    }

    public void LoadPrefab(string strPath, string name, LoadedCallback onLoaded, bool async = true)
    {
        LoadAssetByPath(strPath, name, typeof(GameObject), onLoaded, async);
    }

    public void LoadMusic(string name, LoadedCallback onLoaded, bool async = true)
    {
        string path = string.Format("music/{0}", name);
        LoadAssetByPath(path, name, typeof(AudioClip), onLoaded, async);
    }

    public void LoadFont(string name, LoadedCallback onLoaded, bool async = true, bool inBundle = false)
    {
        string path = string.Format("ui/font/{0}", name);
        LoadAssetByPath(path, name, typeof(Font), onLoaded, async);
    }

    #region LoadScene
    public void LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded)
    {
        var activeSceneName = SceneManager.GetActiveScene().name;
        // 如果当前场景是要加载的场景 直接返回
        if (activeSceneName == name)
        {
            if (onLoaded != null)
            {
                onLoaded(null);
            }
            return;
        }

        if (isAdditive)
        {
            __LoadScene(name, scenePath, isAdditive, onLoaded);
        }
        else //大场景先加载idle过渡
        {
            __LoadScene(name, scenePath, isAdditive, onLoaded);
        }
    }

    private void __LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded, bool async = true)
    {
        SceneLoader sLoader = LoaderPool.Instance.GetLoader<SceneLoader>();
        sLoader.Init(name, scenePath, isAdditive, onLoaded, async);
        StartLoad(sLoader, true);
    }
    #endregion

    //从Bundle中加载资源
    public void LoadAssetByPath(string path, string name, System.Type type, LoadedCallback onLoaded, bool async = true)
    {
        path = "Assets/Data/" + path;
        if (Directory.Exists(path))
        {
            path += "/" + name;
        }

        string suffix = GetSuffixOfAsset(type);
        string fullPath = string.Format("{0}.{1}", path, suffix);
        LoadAsset(fullPath, onLoaded, type, false);
    }

    // 加载资源(Assets目录下,带后缀)
    public void LoadAsset(string path, LoadedCallback onLoaded, System.Type type = null, bool async = true)
    {
        if (!File.Exists(path))
        {
            Debug.LogErrorFormat("Load Asset, Path:[{0}] not exist!  ", path);
            if (onLoaded != null)
            {
                onLoaded(null);
            }
            return;
        }

        AssetLoader aLoader = LoaderPool.Instance.GetLoader<AssetLoader>();
        aLoader.Init(path, type, onLoaded, async);
        StartLoad(aLoader, async);
    }

    void StartLoad(Loader loader, bool async, bool toHead = false)
    {
        // 异步加载或者加载还未准备好,则当做异步处理,外部控制是否加入队列开头
        // 同步加载,并且已经具备加载条件,则直接调用Load进行加载
        if (async || !loader.IsPrepareToLoad())
        {
            if (toHead)
            {
                m_loaderQueue.Insert(0, loader);
            }
            else
            {
                m_loaderQueue.Add(loader);
            }
        }
        else
        {
            m_loadings.Add(loader);
            loader.Load();
        }
    }

    // 卸载关卡场景
    public void UnloadLevelScene(string sceneName, bool immediate)
    {
        SceneManager.UnloadSceneAsync(sceneName);
    }

    private void CallFunc_LoadedBack(LoadedCallback callback, object data)
    {
        if (callback != null)
        {
            callback(data);
        }
    }

    private string GetSuffixOfAsset(System.Type type)
    {
        if (type == typeof(Font))
        {
            return "ttf";
        }
        else if (type == typeof(AudioClip))
        {
            return "ogg";
        }
        else if (type == typeof(GameObject))
        {
            return "prefab";
        }
        else if (type == typeof(TextAsset))
        {
            return "bytes";
        }
        else if (type == typeof(Texture2D) || type == typeof(Sprite))
        {
            return "png";
        }
        return "";
    }
}

4.ab包加载模块:和LoadModule 相比,1.ABLoadModule 需要在初始化前加载manifest文件。2.需要在加载资源前加载ab包以及a包的依赖包。3.需要提供卸载ab包的方法。

/// <summary>
/// 使用ab加载资源(Editor模式下、线上平台)
/// </summary>
public class ABLoadModule : ILoadModule
{
    public int SyncCount;                               // 同步加载并发数
    private AssetBundleManifest m_manifest = null;
    private HashSet<string> m_bundleNames = new HashSet<string>();
    // 加载队列
    List<Loader> m_loadings = new List<Loader>();//正在加载
    List<Loader> m_loaderQueue = new List<Loader>();//等待加载
    Dictionary<string, AssetBundleLoader> m_dicAllLoader = new Dictionary<string, AssetBundleLoader>();

    float m_lastClear = 0;  // 上一次清除时间

    public void Init(int syncCout)
    {
        SyncCount = syncCout;
        LoadManifest();
    }

    public void UnInit()
    {
        for (int i = 0; i < m_loadings.Count; i++)
        {
            m_loadings[i].Stop();
        }
        m_loadings.Clear();

        m_loaderQueue.Clear();
        ABCachePool.Instance.ClearAllCache();
    }

    private List<int> m_lstRmTemp = new List<int>();
    public void Update(float dt)
    {
        for (int i = m_loadings.Count - 1; i >= 0; i--)
        {
            Loader loader = m_loadings[i];
            loader.Update(dt);
            if (loader.IsFinish)
            {
                if (loader.Type == Loader.LoaderType.BUNDLE)
                {
                    AssetBundleLoader bLoader = loader as AssetBundleLoader;
                    if (m_dicAllLoader.ContainsKey(bLoader.BundleName))
                    {
                        m_dicAllLoader.Remove(bLoader.BundleName);
                    }
                }
                m_loadings.RemoveAt(i);
                LoaderPool.Instance.RecycleLoader(loader);
            }
        }

        int remain = Mathf.Min(SyncCount - m_loadings.Count, m_loaderQueue.Count) ;

        for (int i = 0; i < m_loaderQueue.Count; i++)
        {
            Loader loader = m_loaderQueue[i];
            if (loader.Type == Loader.LoaderType.BUNDLE)
            {
                AssetBundleLoader bLoader = loader as AssetBundleLoader;
                if (!bLoader.IsPrepareToLoad())
                {
                    continue;   //如果有依赖未加载完直接跳过
                }
            }
            else if (loader.Type == Loader.LoaderType.BUNDLEASSET)
            {
                BundleAssetLoader bLoader = loader as BundleAssetLoader;
                if (!bLoader.IsPrepareToLoad())
                {
                    continue;   //Asset是否准备好加载
                }
            }

            m_loadings.Add(loader);
            loader.Load();
            loader.Update(dt);
            m_lstRmTemp.Add(i);
            if (m_lstRmTemp.Count >= remain)
            {
                break;
            }
        }

        if (m_lstRmTemp.Count > 0)
        {
            for (int i = m_lstRmTemp.Count - 1; i >= 0; i--)
            {
                m_loaderQueue.RemoveAt(m_lstRmTemp[i]);
            }
            m_lstRmTemp.Clear();
        }

        UpdateAssetBundleCache();
    }

    // 更新AssetBundle缓存(主要执行定时清理)
    public void UpdateAssetBundleCache()
    {
        // 每5秒回收一次
        if (Time.realtimeSinceStartup - m_lastClear < 5)
        {
            return;
        }

        m_lastClear = Time.realtimeSinceStartup;

        /// 检查无引用的AB节点
        ABCachePool.Instance.ClearNoneRefCache(true);
    }

    public void Clear()
    {
        ABCachePool.Instance.ClearNoneRefCache(false);

        Resources.UnloadUnusedAssets();
    }

    #region LoadManifest
    // 加载资源清单
    public void LoadManifest()
    {
        if (m_manifest != null)
        {
            Object.DestroyImmediate(m_manifest, true);
            m_manifest = null;
        }

        m_bundleNames.Clear();

        string manifestName = FileHelper.MANIFEST_NAME;//manifest文件名
        string strFullPath = FileHelper.SearchFilePath(manifestName);//获取manifest路径

        AssetBundleLoader bLoader = LoaderPool.Instance.GetLoader<AssetBundleLoader>();
        bLoader.Init(strFullPath, manifestName, null, delegate (object data) {
            AssetBundleCache ab = data as AssetBundleCache;
            if (ab != null)
            {
                m_manifest = (AssetBundleManifest)ab.LoadAsset("AssetBundleManifest", typeof(AssetBundleManifest));
            }

            ABCachePool.Instance.UnReferenceCache(manifestName, true);  // 不走统一接口是因为manifest文件没有后缀

            if (m_manifest != null)
            {
                string[] bundles = m_manifest.GetAllAssetBundles();

                for (int i = 0; i < bundles.Length; ++i)
                {
                    m_bundleNames.Add(bundles[i]);
                }
            }
        }, false);
        bLoader.Load();
    }
    #endregion

    public void LoadPrefab(string strPath, string name, LoadedCallback onLoaded, bool async = true)
    {
        LoadAssetFromBundle(strPath, name, typeof(GameObject), onLoaded, async);
    }

    public void LoadMusic(string name, LoadedCallback onLoaded, bool async = true)
    {
        string path = string.Format("music/{0}", name);
        LoadAssetFromBundle(path, name, typeof(AudioClip), onLoaded, async);
    }

    public void LoadFont(string name, LoadedCallback onLoaded, bool async = true, bool inBundle = false)
    {
        string path = string.Format("ui/font/{0}", name);
        LoadAssetFromBundle(path, name, typeof(Font), onLoaded, async);
    }

    #region LoadScene
    public void LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded)
    {
        var activeSceneName = SceneManager.GetActiveScene().name;
        // 如果当前场景是要加载的场景 直接返回
        if (activeSceneName == name)
        {
            if (onLoaded != null)
            {
                onLoaded(null);
            }
            return;
        }

        if (isAdditive)
        {
            RealLoadScene(name, scenePath, isAdditive, onLoaded);
        }
        else //大场景先加载idle过渡
        {
            RealLoadScene(name, scenePath, isAdditive, onLoaded);
        }
    }

    private void RealLoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded)
    {
        string abPath = "scenes/" + name;
        LoadAssetBundle(abPath, delegate (object data) {
            if (data == null)
            {
                CallFunc_LoadedBack(onLoaded, null);
                return;
            }
            __LoadScene(name, scenePath, isAdditive, onLoaded);  //场景的bundle在SceneLoader中自动卸载
        });
    }

    private void __LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded, bool async = true)
    {
        SceneLoader sLoader = LoaderPool.Instance.GetLoader<SceneLoader>();
        sLoader.Init(name, scenePath, isAdditive, onLoaded, async);
        StartLoad(sLoader, true);
    }
    #endregion

    //从Bundle中加载资源
    public void LoadAssetFromBundle(string path, string name, System.Type type, LoadedCallback onLoaded, bool async = true)
    {
        LoadAssetBundle(path, (data) => {
            AssetBundleCache abCache = data as AssetBundleCache;
            if (abCache == null)
            {
                Debug.LogWarningFormat("LoadAssetFromBundle, load ab fail:{0}", path);
                CallFunc_LoadedBack(onLoaded, null);
                return;
            }

            // 开启任务去做加载
            BundleAssetLoader assetLoader = LoaderPool.Instance.GetLoader<BundleAssetLoader>();
            assetLoader.Init(abCache, name, type, onLoaded, async);
            StartLoad(assetLoader, async);
        }, async);
    }

    // 加载AssetBundle(先从persistentData读,没有找到则从streamingAssets读,带后缀)
    public void LoadAssetBundle(string path, LoadedCallback onLoaded, bool async = true)
    {
        string name = FileHelper.GenBundlePath(path);
        if (!HasAssetBundle(name))
        {
            if (onLoaded != null)
            {
                onLoaded(null);
            }
            return;
        }

        // 加载依赖
        LoadDependencies(name, async);

        // 检查是否有缓存 有缓存说明依赖资源也是加载过了的
        AssetBundleCache abCache = ABCachePool.Instance.ReferenceCacheByName(name);
        if (abCache != null)
        {
            if (onLoaded != null)
            {
                onLoaded(abCache);
            }
            return;
        }

        string fullpath = FileHelper.SearchFilePath(name);
        AssetBundleLoader bLoader = null;
        m_dicAllLoader.TryGetValue(name, out bLoader);
        if (bLoader == null)
        {
            string[] dependencies = m_manifest.GetDirectDependencies(name);

            bLoader = LoaderPool.Instance.GetLoader<AssetBundleLoader>();
            bLoader.Init(fullpath, name, dependencies, onLoaded, async);
            m_dicAllLoader.Add(name, bLoader);
            StartLoad(bLoader, async);
        }
        else
        {
            if (onLoaded != null)
            {
                bLoader.AddLoadedCallback(onLoaded);
            }
            else
            {
                bLoader.AddReferenced();
            }
        }
    }

    // 依赖
    // 加载依赖
    //asyncInFact 实际加载方式,如果依赖bundle是异步加载并且正在加载中,那么整个bundle的加载方式变成异步加载
    void LoadDependencies(string name, bool async)
    {
        if (m_manifest == null)
        {
            return;
        }

        string[] dependencies = m_manifest.GetDirectDependencies(name);
        for (int i = 0; i < dependencies.Length; ++i)
        {
            LoadAssetBundle(dependencies[i], null, async);
        }
    }

    void StartLoad(Loader loader, bool async, bool toHead = false)
    {
        // 异步加载或者加载还未准备好,则当做异步处理,外部控制是否加入队列开头
        // 同步加载,并且已经具备加载条件,则直接调用Load进行加载
        if (async || !loader.IsPrepareToLoad())
        {
            if (toHead)
            {
                m_loaderQueue.Insert(0, loader);
            }
            else
            {
                m_loaderQueue.Add(loader);
            }
        }
        else
        {
            m_loadings.Add(loader);
            loader.Load();
        }
    }

    public bool HasAssetBundle(string path)
    {
        path = path.Replace("/", "_").ToLower();
        return m_bundleNames.Count == 0 || m_bundleNames.Contains(path) || string.Equals(path, FileHelper.MANIFEST_NAME);
    }

    // 卸载关卡场景
    public void UnloadLevelScene(string sceneName, bool immediate)
    {
        SceneManager.UnloadSceneAsync(sceneName);
        UnloadSceneAssetBundle(sceneName, immediate);
    }

    // 卸载场景的AssetBundle
    public void UnloadSceneAssetBundle(string sceneName, bool immediate)
    {
        if (Config.Instance.UseAssetBundle)
        {
            string strABPath = "scenes/" + sceneName;
            UnloadAssetBundle(strABPath, immediate);
        }
    }

    // 卸载AssetBundle
    public void UnloadAssetBundle(string path, bool immediate = false)
    {
        string name = FileHelper.GenBundlePath(path);
        AssetBundleCache cache = ABCachePool.Instance.UnReferenceCache(name, immediate);
        if (cache != null)
        {
            UnloadDependencies(name, immediate);
        }
    }

    // 卸载依赖
    void UnloadDependencies(string name, bool immediate)
    {
        if (m_manifest == null)
        {
            return;
        }

        string[] dependencies = m_manifest.GetDirectDependencies(name);
        for (int i = 0; i < dependencies.Length; ++i)
        {
            UnloadAssetBundle(dependencies[i], immediate);
        }
    }

    private void CallFunc_LoadedBack(LoadedCallback callback, object data)
    {
        if (callback != null)
        {
            callback(data);
        }
    }
}
原文地址:https://www.cnblogs.com/wang-jin-fu/p/11218659.html