Resource Manager
以双向链表为基础的资源池
基础资源同步加载
基础资源异步加载
基本资源卸载
清空缓存
预加载
为ObjectPoolManager
提供同步异步资源加载
基础资源同步加载 在ResouceManager中有如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 using System.Collections.Generic;using UnityEngine;public class ResourceManager : SingletonPattern <ResourceManager >{ public bool m_LoadFormAssetBundle = false ; protected CMapList<ResourceItem> m_NoReferenceAssetMapList = new CMapList<ResourceItem>(); public Dictionary<uint ,ResourceItem> AssetDic { get ; set ; } = new Dictionary<uint ,ResourceItem>(); public T LoadResource <T >(string path ) where T : UnityEngine.Object { if (string .IsNullOrEmpty(path)) { return null ; } uint crc = CRC32.GetCRC32(path); ResourceItem item = GetCacheResourceItem(crc); if (item != null ) { return item.m_Obj as T; } T obj = null ; #if UNITY_EDITOR if (!m_LoadFromAssetBundle) { item = AssetBundleManager.Instance.FindResourceItem (crc); if (item != null && item.m_Obj != null ) { obj = item.m_Obj as T; } else { if (item == null ) { item = new ResourceItem { m_Crc = crc, }; } obj = LoadAssetByEditor<T>(path); } } #endif if (obj == null ) { item = AssetBundleManager.Instance.LoadResourceAssetBundle(crc); if (item != null && item.m_AssetBundle != null ) { if (item.m_Obj != null ) { obj = item.m_Obj as T; } else { obj = item.m_AssetBundle.LoadAsset<T>(item.m_AssetName); } } } CacheResource(path,ref item,crc,obj); return obj; } public bool ReleaseResource (Object obj,bool destroyObj = false ) { if (obj == null ) return false ; ResourceItem item = null ; foreach (ResourceItem resource in AssetDic.Values) { if (resource.m_GUID == obj.GetInstanceID()) { item = resource; } } if (item == null ) { Debug.LogError("AssetDic里不存在该资源:" + obj.name + " 可能释放了多次" ); return false ; } item.RefCount--; DestroyResourceItem(item,destroyObj); return true ; } void CacheResource (string path, ref ResourceItem item, uint crc, UnityEngine.Object obj,int addrefcount = 1 ) { WashOut(); if (item == null ) { Debug.LogError("ResourceItem is null, path: " + path); } if (obj == null ) { Debug.LogError("ResourceLoad Fail: " + path); } item.m_Obj = obj; item.m_GUID = obj.GetInstanceID(); item.m_LastUseTime = Time.realtimeSinceStartup; item.RefCount += addrefcount; ResourceItem oldItem = null ; if (AssetDic.TryGetValue(item.m_Crc, out oldItem)) { AssetDic[item.m_Crc] = item; } else { AssetDic.Add(item.m_Crc, item); } } protected void WashOut () { } protected void DestroyResourceItem (ResourceItem item, bool destroyCache = false ) { if (item == null ||item.RefCount>0 ) { return ; } if (!destroyCache) { m_NoReferenceAssetMapList.InsertToHead(item); return ; } if (!AssetDic.Remove(item.m_Crc)) { return ; } AssetBundleManager.Instance.ReleaseAsset (item); if (item.m_Obj != null ) { item.m_Obj = null ; } } #if UNITY_EDITOR protected T LoadAssetByEditor <T >(string path ) where T:UnityEngine.Object { return UnityEditor.AssetDatabase.LoadAssetAtPath<T>(path); } #endif ResourceItem GetCacheResourceItem (uint crc,int addrefcount = 1 ) { ResourceItem item; if (AssetDic.TryGetValue(crc, out item)) { if (item != null ) { item.RefCount += addrefcount; item.m_LastUseTime = Time.realtimeSinceStartup; } } return item; } }
解释 m_NoReferenceAssetMapList
缓存引用计数为零的ResouceItem
,如果一个ResouceItem
维护的资源经常需要加载,那么就缓存在这个双向列表里,在这个双向列表内部的资源是执行内存清理和重用的主要目标。
AssetDic
缓存已经加载的资源,如果有多个对象引用到了相同的资源,直接调用到GetCacheResourceItem
方法,并且增加引用计数;如果有对象需要卸载,调用到ReleaseResource
方法,ReleaseResource
方法会调用到DestroyResourceItem
方法,后者会判断是否减少引用计数以及直接卸载对应的AssetBundle
公共方法:
1 2 public T LoadResource <T >(string path ) where T : UnityEngine.Object {}public bool ReleaseResource (Object obj,bool destroyObj = false ) {}
ResourceItem ResourceItem
类是我们在运行时维护各个资源的主要的类,每一个ResourceItem
实例都表示一个单独的资源,比如一个音频文件,一个贴图等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class ResourceItem { public uint m_Crc = 0 ; public string m_AssetName = string .Empty; public string m_AssetBundleName = string .Empty; public List<string > m_DependAssetBundle = null ; public AssetBundle m_AssetBundle = null ; public Object m_Obj = null ; public int m_GUID = 0 ; public float m_LastUseTime = 0.0f ; protected int m_RefCount = 0 ; public int RefCount { get { return m_RefCount; } set { m_RefCount = value ; if (m_RefCount < 0 ) { Debug.LogError("refcount < 0" + m_RefCount + " ," + (m_Obj != null ? m_Obj.name : "name is null" )); } } } }
创建时机 它是在AssetBundle Manager中,加载资源配置表时生成的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public bool LoadAssetBundleConfig (){ m_ResourceItemDic.Clear(); string configPath = Application.streamingAssetsPath + "/assetbundleconfig" ; AssetBundle configAB = AssetBundle.LoadFromFile(configPath); TextAsset textAsset = configAB.LoadAsset<TextAsset>("AssetBundleConfig" ); if (textAsset == null ) { Debug.LogError("AssetBundleConfig isn't exist" ); return false ; } using (MemoryStream ms = new MemoryStream(textAsset.bytes)) { BinaryFormatter bf = new BinaryFormatter(); AssetBundleConfig abConfig = (AssetBundleConfig)bf.Deserialize(ms); for (int i = 0 ; i < abConfig.ABBasesList.Count; i++) { ABBase abBase = abConfig.ABBasesList[i]; ResourceItem resItem = new ResourceItem { m_Crc = abBase.Crc, m_AssetName = abBase.AssetName, m_AssetBundleName = abBase.ABName, m_DependAssetBundle = abBase.ABDependence, }; if (m_ResourceItemDic.ContainsKey(resItem.m_Crc)) { Debug.LogError("重复的Crc 资源名:" + resItem.m_AssetName + "AB包名:" + resItem.m_AssetBundleName); } else { m_ResourceItemDic.Add(resItem.m_Crc, resItem); } } return true ; } }
在此时只是生成阶段,每个ResourceItem
实例只包含了资源的基本信息,资源所在的AssetBundle
都是之后调用Asset Bundle Manager的LoadResourceAssetBundle
方法实现,而此方法又是从Resorce Manager的LoadResource
调用,LoadResource
调用时也将资源本身也加载进了内存中
移除阶段 当我们移除ResourceItem
时,调用的是Resource Manager的DestoryResourceItem
方法,在这个方法内,如果指示不清除缓存,ResouceItem
会被储存进双向链表里,我们只清除AssetDic
的缓存,这样只是表面上被移除了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 protected void DestroyResourceItem (ResourceItem item, bool destroyCache = false ) { if (item == null ||item.RefCount>0 ) { return ; } if (!AssetDic.Remove(item.m_Crc)) { return ; } if (!destroyCache) { m_NoReferenceAssetMapList.InsertToHead(item); return ; } AssetBundleManager.Instance.ReleaseAsset (item); if (item.m_Obj != null ) { item.m_Obj = null ; } #if UNITY_EDITOR Resources.UnloadUnusedAssets(); #endif }
当一个ResourceItem
被表面上“移除”时,我们再次调用资源加载方法LoadResource
,此时虽然我们的AssetDic
没有了对ResourceItem
的引用,但是Asset Bundle Manager内部m_ResourceItemDic
依然保留着对ResourceItem
的引用(当然双向链表里面也有一份)