Resource Manager

  1. 以双向链表为基础的资源池
  2. 基础资源同步加载
  3. 基础资源异步加载
  4. 基本资源卸载
  5. 清空缓存
  6. 预加载
  7. 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;

//缓存引用计数为零的Resource Item
//达到缓存最大时,释放这个列表内最早没用的资源
//ResourceItem是读取AssetBundle配置表时生成的,一开始的引用计数都为零,甚至ResourceItem内的AssetBundle都是空的
protected CMapList<ResourceItem> m_NoReferenceAssetMapList = new CMapList<ResourceItem>();
//缓存正在引用的资源列表
public Dictionary<uint,ResourceItem> AssetDic { get; set; } = new Dictionary<uint,ResourceItem>();
/// <summary>
/// 同步资源加载,外部直接调用,仅加载不需要实例化的资源
/// </summary>
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);//??必须保证先加载了AB包的配置才能拿到
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;
}
/// <summary>
/// 不需要实例化的资源卸载
/// </summary>
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;
}
/// <summary>
/// 缓存加载的资源
/// </summary>
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;
//更新缓存列表,相同crc的Item被新的替代,一个容错判断
ResourceItem oldItem = null;
if(AssetDic.TryGetValue(item.m_Crc, out oldItem))
{
AssetDic[item.m_Crc] = item;
}
else
{
AssetDic.Add(item.m_Crc, item);
}
}
/// <summary>
/// 缓存太多时,清除最早没有使用的资源
/// </summary>
protected void WashOut()
{
//当前内存占用大于80%时清除最早没用的资源
//{
// if(m_NoReferenceAssetMapList.Size() <= 0)
// {
// break;
// }
// ResourceItem item = m_NoReferenceAssetMapList.Back();
// DestroyResourceItem(item,true );
// m_NoReferenceAssetMapList.Pop();
//}
}
/// <summary>
/// 回收一个资源
/// </summary>
/// <param name="destroyCache">是否清除缓存</param>
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;
}
//释放Asset Bundle引用
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
/// <summary>
/// 获取缓存的ResourceItem
/// </summary>
/// <param name="crc"></param>
/// <param name="addrefcount">引用的数量,有可能有多个引用</param>
/// <returns></returns>
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;
//if (item.RefCount <= 1)
//{
// m_NoReferenceAssetMapList.Remove(item);
//}
}
}
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
{
//该资源路径的crc
public uint m_Crc = 0;
//该资源的文件名
public string m_AssetName = string.Empty;
//该资源所在的AssetBundle名字
public string m_AssetBundleName = string.Empty;
//该资源所依赖的AssetBundle名字
public List<string> m_DependAssetBundle = null;
//该资源加载完的AB包
public AssetBundle m_AssetBundle = null;
//----------------------------------------------
//资源对象,在Resource Manager使用
public Object m_Obj = null;
//资源GUID
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
    /// <summary>
/// 回收一个资源
/// </summary>
/// <param name="destroyCache">是否清除缓存</param>
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);//如果不清除缓存,ResourceItem会被添加进双向链表
return;
}

//释放Asset Bundle引用
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的引用(当然双向链表里面也有一份)