U4S4L18——引用计数规则

引用计数规则

当我们通过加载使用可寻址资源时,Addressables​会在内部帮助我们进行引用计数
使用资源时,引用计数+1
释放资源时,引用计数-1
当可寻址资源的引用为0时,就可以卸载它了

为了避免内存泄露(不需要使用的内容残留在内存中不释放),我们要保证**加载资源和卸载资源是配对使用的**

注意:释放的资源不一定立即从内存中卸载,在卸载资源所属的AB包之前,不会释放资源使用的内存
(比如自己(某个资源)所在的AB包被别人(另一个资源)使用时,这时AB包不会被卸载,所以自己(某个资源)还在内存中)
我们可以使用 Resources.UnloadUnusedAssets​ 卸载资源(建议在切换场景时调用)

AB包也有自己的引用计数(Addressables​把它也视为可寻址资源)
从AB包中加载资源时,引用计数+1
从AB包中卸载资源时,引用计数-1
当AB包引用计数为0时,意味着不再使用了,这时会从内存中卸载

总结:Addressables​内部会通过引用计数帮助我们管理内存,我们只需要保证 加载和卸载资源配对使用 即可

举例说明引用计数

我们创建两个一样的资源,然后一个一个的释放他们的资源句柄,观察他们创建出来的对象变化

注意:使用第三种模式加载资源(从AB包中加载)

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
private List<AsyncOperationHandle<GameObject>> list = new List<AsyncOperationHandle<GameObject>>();

void Update()
{
//创建对象 记录异步操作句柄
if (Input.GetKeyDown(KeyCode.Space))
{
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");
handle.Completed += (obj) =>
{
Instantiate(obj.Result);
};
list.Add(handle);
}

//从创建对象中,释放异步操作句柄资源
if (Input.GetKeyDown(KeyCode.Q))
{
if (list.Count > 0)
{
Addressables.Release(list[0]);
list.RemoveAt(0);
}
}
}

按下一次空格键,再按下一次Q键,实例化出来的对象的材质会丢失,因为引用计数归0了,AB包及其对应的资源都被卸载

image

按下两次空格键,再按下一次Q键,实例化出来的两个对象的材质都不会丢失,因为引用计数不为0,AB包及其对应的资源都没有被卸载

image

但如果再按下一次Q键,实例化出来的两个对象的材质都会丢失,因为引用计数归0了,AB包及其对应的资源都被卸载

image

回顾之前写的资源管理器

我们之前写的资源管理器AddressablesManager
通过自己管理异步加载的返回句柄会让系统自带的引用计数功能不起作用
因为我们不停的在复用一个句柄

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Update()
{
//创建对象 记录异步操作句柄
if (Input.GetKeyDown(KeyCode.Space))
{
AddressableMgr.Instance.LoadAssetAsync<GameObject>("Cube", (obj) =>
{
Instantiate(obj.Result);
});
}

//从创建对象中,释放异步操作句柄资源
if (Input.GetKeyDown(KeyCode.Q))
{
AddressableMgr.Instance.Release<GameObject>("Cube");
}
}

按下两次空格键,再按下一次Q键,实例化出来的两个对象的材质都会丢失,因为引用计数归0了,AB包及其对应的资源都被卸载

image

由于我们自己实现的管理器复用的是一个句柄,且没有实现引用计数
这导致通过管理器去加载,即使加载了多个资源,只要卸载一次就会完全卸载掉内容

如果要避免这种情况,我们就必须要在自己的管理器内实现一个引用计数

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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class AddressablesInfo
{
//记录 异步操作句柄
public AsyncOperationHandle handle;
//记录 引用计数
public uint count;

public AddressablesInfo(AsyncOperationHandle handle)
{
this.handle = handle;
//实例化时自动引用计数+1
this.count += 1;
}
}

public class AddressableMgr
{
private static AddressableMgr instance;
public static AddressableMgr Instance => instance ??= new AddressableMgr();

public Dictionary<string, AddressablesInfo> resDic = new Dictionary<string, AddressablesInfo>();

private AddressableMgr() { }

public void LoadAssetAsync<T>(string name, Action<AsyncOperationHandle<T>> callBack)
{
string keyName = name + "_" + typeof(T).Name;
AsyncOperationHandle<T> handle;
//如果加载过资源
if (resDic.ContainsKey(keyName))
{
handle = resDic[keyName].handle.Convert<T>();
//引用计数+1
resDic[keyName].count += 1;
if (handle.IsDone)
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{

callBack?.Invoke(handle);
}
else
{
Debug.LogWarning(keyName + "资源加载失败!");
resDic.Remove(keyName);
}
}
else
{
handle.Completed += (obj) =>
{
if (obj.Status == AsyncOperationStatus.Succeeded)
{
callBack?.Invoke(obj);
}
else
{
Debug.LogWarning(keyName + "资源加载失败!");
if (resDic.ContainsKey(keyName))
resDic.Remove(keyName);
}
};
}
return;
}
//如果未加载过资源
handle = Addressables.LoadAssetAsync<T>(name);
handle.Completed += (obj) =>
{
if (obj.Status == AsyncOperationStatus.Succeeded)
callBack?.Invoke(obj);
else
{
Debug.LogWarning(keyName + "资源加载失败!");
if (resDic.ContainsKey(keyName))
resDic.Remove(keyName);
}
};
//引用计数+1
AddressablesInfo info = new AddressablesInfo(handle);
resDic.Add(keyName, info);
}

//异步加载多个资源 或者 加载指定资源
public void LoadAssetAsync<T>(Addressables.MergeMode mode, Action<T> callBack, params string[] keys)
{
//构建一个KeyName
List<string> keyList = new List<string>(keys);
string keyName = "";
foreach (string key in keyList)
{
keyName += key + "_";
}
keyName += typeof(T).Name;

//判断是否存在已经加载过的内容
AsyncOperationHandle<IList<T>> handle;
//如果存在
if (resDic.ContainsKey(keyName))
{
handle = resDic[keyName].handle.Convert<IList<T>>();
//引用计数+1
resDic[keyName].count++;
//如果已经全部加载完毕
if (handle.IsDone)
{
foreach (T item in handle.Result)
{
callBack?.Invoke(item);
}
}
//如果还没有全部加载完毕
else
{
handle.Completed += (obj) =>
{
//加载成功才调用外部传入的委托函数
if (obj.Status == AsyncOperationStatus.Succeeded)
{
foreach (T item in handle.Result)
{
callBack?.Invoke(item);
}
}
}; //end handle.Completed
}
return;
}

//如果不存在
handle = Addressables.LoadAssetsAsync<T>(keyList, callBack, mode);
handle.Completed += (obj) =>
{
if (obj.Status == AsyncOperationStatus.Failed)
{
Debug.LogError("资源加载失败" + keyName);
if (resDic.ContainsKey(keyName))
resDic.Remove(keyName);
}
else if (obj.Status == AsyncOperationStatus.Succeeded)
{
foreach (T item in handle.Result)
{
callBack?.Invoke(item);
}
}
};
//引用计数+1
AddressablesInfo info = new AddressablesInfo(handle);
resDic.Add(keyName, info);
}

//异步加载多个资源 或者 加载指定资源
public void LoadAssetAsync<T>(Addressables.MergeMode mode, Action<AsyncOperationHandle<IList<T>>> callBack, params string[] keys)
{
//构建一个KeyName
List<string> keyList = new List<string>(keys);
string keyName = "";
foreach (string key in keyList)
{
keyName += key + "_";
}
keyName += typeof(T).Name;
//判断是否存在已经加载过的内容
AsyncOperationHandle<IList<T>> handle;
//如果存在
if (resDic.ContainsKey(keyName))
{
handle = resDic[keyName].handle.Convert<IList<T>>();
//引用计数+1
resDic[keyName].count++;
//如果已经全部加载完毕
if (handle.IsDone)
{
callBack?.Invoke(handle);
}
//如果还没有全部加载完毕
else
{
handle.Completed += (obj) =>
{
//加载成功才调用外部传入的委托函数
if (obj.Status == AsyncOperationStatus.Succeeded)
{
callBack?.Invoke(obj);
}
}; //end handle.Completed
}
return;
}
//如果不存在
handle = Addressables.LoadAssetsAsync<T>(keyList, null, mode);
handle.Completed += (obj) =>
{
if (obj.Status == AsyncOperationStatus.Failed)
{
Debug.LogError("资源加载失败" + keyName);
if (resDic.ContainsKey(keyName))
resDic.Remove(keyName);
}
else if (obj.Status == AsyncOperationStatus.Succeeded)
{
callBack?.Invoke(obj);
}
};
//引用计数+1
AddressablesInfo info = new AddressablesInfo(handle);
resDic.Add(keyName, info);
}

public void Release<T>(params string[] keys)
{
//构建一个KeyName
List<string> keyList = new List<string>(keys);
string keyName = "";
foreach (string key in keyList)
{
keyName += key + "_";
}
keyName += typeof(T).Name;

if (!resDic.ContainsKey(keyName))
return;
//引用计数-1
resDic[keyName].count -= 1;
if (resDic[keyName].count == 0)
{
Addressables.Release(resDic[keyName].handle.Convert<T>());
resDic.Remove(keyName);
}
}

public void Release<T>(string name)
{
string keyName = name + "_" + typeof(T).Name;
if (!resDic.ContainsKey(keyName))
return;
//引用计数-1
resDic[keyName].count -= 1;
if (resDic[keyName].count == 0)
{
Addressables.Release(resDic[keyName].handle.Convert<T>());
resDic.Remove(keyName);
}
}

public void Clear()
{
foreach (AddressablesInfo info in resDic.Values)
{
Addressables.Release(info.handle);
}
resDic.Clear();
AssetBundle.UnloadAllAssetBundles(true);
Resources.UnloadUnusedAssets();
GC.Collect();
}
}