UFL5-2——优化Resources资源加载模块的异步加载

异步加载问题指的是什么

每次进行异步加载时,都会开启一个协同程序,虽然Resources​资源会在内部进行缓存,加载已加载过的资源,性能消耗不会太大
但是每次开启协程的过程也会浪费性能,因此我们希望对上节课的ResManager​进行优化
不依赖Resources​内部的缓存机制,而是自己来管理已经加载过的资源,从而解决异步加载时协同程序的频繁开启造成的性能浪费

例如下面的代码就是加载的一模一样的资源

1
2
3
4
5
6
7
8
9
ResManager.Instance.LoadAsync<GameObject>("Test", (obj) =>
{
Instantiate(obj);
});

ResManager.Instance.LoadAsync("Test", typeof(GameObject), (obj) =>
{
Instantiate(obj as GameObject);
});

我们想要达到的目的是:通过一个字典记录已经加载过的资源,每次在进行资源加载时,如果发现是已经加载过的资源,我们直接使用即可

制作思路和具体实现

  1. 字典容器结构设计

    主要考虑点

    • key​ - 资源名(路径 + 类型 拼接而成 resName = $"{path}_{typeof(T).Name}"​ )

    • value​ - 自定义数据结构类ResInfo<T>​:资源、加载完执行的委托、协程对象等

      其中,不同类型的资源必然会使用不同的协程参数,为了能够让字典装载他们,需要再声明一个ResInfoBase​并让ResInfo<T>​继承

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      /// <summary>
      /// 资源消息基类
      /// </summary>
      public abstract class ResInfoBase { }

      /// <summary>
      /// 资源消息对象 主要用于存储资源消息,异步加载委托消息,异步加载协程消息
      /// </summary>
      /// <typeparam name="T"></typeparam>
      public class ResInfo<T> : ResInfoBase
      {
      public T asset; //资源
      public UnityAction<T> callBack; //待异步资源加载完后,传递资源到外部的委托,加载完毕后清空
      public Coroutine coroutine; //记录异步加载时 开启的协同程序,加载完毕后清空
      }

    接下来就可以声明字典容器

    1
    2
    //主要用于存储加载过的资源或者加载中的资源的容器
    private Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();
  2. 修改异步加载相关逻辑

    • LoadAsync<T>​内

      • 字典中不存在资源记录时

        声明一个ResInfo<T>​,记录回调函数,开启协同程序进行加载,同时将协同程序也记录下来
        并且此时就要将声明出来的ResInfo<T>​记录进字典中(这样可以避免重复异步加载)

      • 字典中存在资源记录时,通过ResInfo<T>.asset​是否为null​判断资源是否加载完毕

        1. 资源还没加载完 —— 记录委托,等待资源加载完毕后一起执行
        2. 资源已经加载完 —— 直接执行回调函数,传入加载好的资源
    • ReallyLoadAsync<T>​内

      协程不再需要额外传入回调函数,可直接调用字典内的ResInfo<T>.callBack
      在加载完毕后,将资源记录到ResInfo<T>.asset​内,
      调用委托将资源传递出去后,将消息记录中的回调函数和协程的引用释放掉,避免内存泄漏

    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
    //主要用于存储加载过的资源或者加载中的资源的容器
    private Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();

    // 异步加载资源的方法
    public void LoadAsync<T>(string path, UnityAction<T> callBack) where T : UnityEngine.Object
    {
    //资源的唯一ID,通过路径名_资源类型拼接而成
    string resName = $"{path}_{typeof(T).Name}";
    ResInfo<T> info;
    //不存在消息记录时,说明资源未加载过
    if (!resDic.ContainsKey(resName))
    {
    info = new ResInfo<T>(); //声明一个 资源信息对象
    resDic.Add(resName, info); //将资源记录添加到资源内(资源没有加载成功)
    info.callBack += callBack; //记录传入的委托函数,一会加载完成了再使用
    //通过协同程序去异步加载资源,并记录该协同程序
    info.coroutine = MonoManager.Instance.StartCoroutine(ReallyLoadAsync<T>(path));
    }
    //存在消息记录时,资源已经加载过
    else
    {
    info = resDic[resName] as ResInfo<T>; //从字典中取出资源信息
    //资源尚未加载成功
    if (info.asset == null)
    info.callBack += callBack; //将回调函数添加到委托内,等待加载完毕后一起执行
    //资源已经加载完毕
    else
    callBack?.Invoke(info.asset); //直接执行传入的回调函数
    }
    }

    private IEnumerator ReallyLoadAsync<T>(string path) where T : UnityEngine.Object
    {
    //异步加载资源
    ResourceRequest req = Resources.LoadAsync<T>(path);
    //等待资源加载结束后,才会继续执行yield return后面的代码
    yield return req;
    string resName = $"{path}_{typeof(T).Name}";
    //资源加载结束,将资源传到外部的委托函数去进行调用
    if (resDic.ContainsKey(resName))
    {
    ResInfo<T> resInfo = resDic[resName] as ResInfo<T>; //取出资源消息
    resInfo.asset = req.asset as T; //将资源记录到资源信息内
    resInfo.callBack?.Invoke(resInfo.asset); //将加载出来的资源传递出去
    //加载完毕后,这些引用就可以清空,避免引用的占用带来的内存泄露问题
    resInfo.callBack = null;
    resInfo.coroutine = null;
    }
    }

    对于非泛型异步加载方法LoadAsync​,该方法使用的ResInfo<>​泛型参数默认使用UnityEngine.Object​即可
    但是,由于不同泛型参数存储的ResInfo<>​存储的委托是不同的,因此LoadAsync​和LoadAsync<>​混用很可能出现问题
    因此,我们规定LoadAsync​和LoadAsync<>​不可混用即可

    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
    //主要用于存储加载过的资源或者加载中的资源的容器
    private Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();

    [Obsolete("注意:建议使用泛型方法,如果需要使用Type加载,请不要和泛型加载混用去加载同类型同名资源")]
    public void LoadAsync(string path, Type type, UnityAction<UnityEngine.Object> callBack)
    {
    //资源的唯一ID,通过路径名_资源类型拼接而成
    string resName = $"{path}_{type.Name}";
    ResInfo<UnityEngine.Object> info;
    //不存在消息记录时,说明资源未加载过
    if (!resDic.ContainsKey(resName))
    {
    info = new ResInfo<UnityEngine.Object>(); //声明一个 资源信息对象
    resDic.Add(resName, info); //将资源记录添加到资源内(资源没有加载成功)
    info.callBack += callBack; //记录传入的委托函数,一会加载完成了再使用
    //通过协同程序去异步加载资源,并记录该协同程序
    info.coroutine = MonoManager.Instance.StartCoroutine(ReallyLoadAsync(path, type));
    }
    //存在消息记录时,资源已经加载过
    else
    {
    info = resDic[resName] as ResInfo<UnityEngine.Object>; //从字典中取出资源信息
    //资源尚未加载成功
    if (info.asset == null)
    info.callBack += callBack; //将回调函数添加到委托内,等待加载完毕后一起执行
    //资源已经加载完毕
    else
    callBack?.Invoke(info.asset); //直接执行传入的回调函数
    }
    }

    private IEnumerator ReallyLoadAsync(string path, Type type)
    {
    //异步加载资源
    ResourceRequest req = Resources.LoadAsync(path, type);
    //等待资源加载结束后,才会继续执行yield return后面的代码
    yield return req;
    string resName = $"{path}_{type.Name}";
    //资源加载结束,将资源传到外部的委托函数去进行调用
    if (resDic.ContainsKey(resName))
    {
    ResInfo<UnityEngine.Object> resInfo = resDic[resName] as ResInfo<UnityEngine.Object>; //取出资源消息
    resInfo.asset = req.asset; //将资源记录到资源信息内
    resInfo.callBack?.Invoke(resInfo.asset); //将加载出来的资源传递出去
    //加载完毕后,这些引用就可以清空,避免引用的占用带来的内存泄露问题
    resInfo.callBack = null;
    resInfo.coroutine = null;
    }
    }
  3. 修改同步加载Load​相关逻辑

    修改同步加载Load​逻辑主要是为了解决:已加载过资源但重复加载的情况,和如何处理先执行异步加载,未加载完时又执行同步加载的情况

    • 字典中不存在资源记录时

      直接同步加载资源记录即可

    • 字典中存在资源记录时

      1. 资源还没加载完 —— 停止协程
      2. 资源已经加载完 —— 直接使用
    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
    //主要用于存储加载过的资源或者加载中的资源的容器
    private Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();

    private ResManager() { }

    // 同步加载资源的方法
    public T Load<T>(string path) where T : UnityEngine.Object
    {
    //资源的唯一ID,通过路径名_资源类型拼接而成
    string resName = $"{path}_{typeof(T).Name}";
    ResInfo<T> info;
    //字典中不存在资源时
    if (!resDic.ContainsKey(resName))
    {
    //直接同步加载,并且记录资源消息到字典中,方便下次直接取出来用
    T res = Resources.Load<T>(path);
    info = new ResInfo<T>();
    info.asset = res;
    resDic.Add(resName, info);
    return res;
    }
    else
    {
    //取出字典的记录
    info = resDic[resName] as ResInfo<T>;
    //存在异步加载且还在加载中
    if (info.asset == null)
    {
    //停止异步加载,直接采用同步加载的方式加载,并记录
    MonoManager.Instance.StopCoroutine(info.coroutine);
    T res = Resources.Load<T>(path);
    info.asset = res;
    //将同步加载出来的内容传递到已存在的回调函数内执行
    info.callBack?.Invoke(res);
    //执行结束回调,将记录的回调和协程清空,避免内存泄露
    info.callBack = null;
    info.coroutine = null;
    return res;
    }
    //已经加载过资源
    else
    {
    return info.asset;
    }
    }
    }
  4. 修改卸载资源UnloadAsset​相关逻辑

    • 字典中存在资源记录时

      1. 资源还没加载完 —— 记录删除标识,待加载完后真正移除 或者 停止协程,并且移除
      2. 资源已经加载完 —— 直接卸载,并且移除字典中资源记录

    首先为ResInfo<>​添加一个标识,来表示是否需要移除

    1
    2
    3
    4
    5
    6
    7
    public class ResInfo<T> : ResInfoBase
    {
    public T asset; //资源
    public UnityAction<T> callBack; //用于异步资源加载完后 传递资源到外部的委托
    public Coroutine coroutine; //用于异步加载时 开启的协同程序
    public bool isDel; //是否需要移除
    }

    然后,根据以上思路修改UnloadAsset

    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 Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();

    // 指定卸载一个资源
    public void UnloadAsset<T>(string path)
    {
    string resName = $"{path}_{typeof(T).Name}";
    //是否存在对应资源
    if (resDic.ContainsKey(resName))
    {
    ResInfo<T> resInfo = resDic[resName] as ResInfo<T>;
    //资源已经加载结束
    if (resInfo.asset != null)
    {
    //从字典移除,通过api卸载资源
    resDic.Remove(resName);
    Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);
    }
    //资源正在异步加载中
    else
    {
    resInfo.isDel = true; //改变标识,代表待移除
    }
    }
    }

    对于需要卸载但是资源还没加载完的情况,我们需要在修改标识后,在ReallyLoadAsync<>​中在加载完后重新执行UnloadAsset<>

    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
    //主要用于存储加载过的资源或者加载中的资源的容器
    private Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();

    private IEnumerator ReallyLoadAsync<T>(string path) where T : UnityEngine.Object
    {
    //异步加载资源
    ResourceRequest req = Resources.LoadAsync<T>(path);
    //等待资源加载结束后,才会继续执行yield return后面的代码
    yield return req;
    string resName = $"{path}_{typeof(T).Name}";
    //资源加载结束,将资源传到外部的委托函数去进行调用
    if (resDic.ContainsKey(resName))
    {
    ResInfo<T> resInfo = resDic[resName] as ResInfo<T>; //取出资源消息
    resInfo.asset = req.asset as T; //将资源记录到资源信息内
    //如果发现需要删除,再去移除资源
    if (resInfo.isDel)
    {
    UnloadAsset<T>(path);
    }
    else
    {
    resInfo.callBack?.Invoke(resInfo.asset); //将加载出来的资源传递出去
    //加载完毕后,这些引用就可以清空,避免引用的占用带来的内存泄露问题
    resInfo.callBack = null;
    resInfo.coroutine = null;
    }
    }
    }

    对于不需要泛型的资源消息,需要额外声明不需要泛型的UnloadAsset​,同时修改ReallyLoadAsync​的逻辑

    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
    //主要用于存储加载过的资源或者加载中的资源的容器
    private Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();

    public void UnloadAsset(string path, Type type)
    {
    string resName = $"{path}_{type.Name}";
    //是否存在对应资源
    if (resDic.ContainsKey(resName))
    {
    ResInfo<UnityEngine.Object> resInfo = resDic[resName] as ResInfo<UnityEngine.Object>;
    //资源已经加载结束
    if (resInfo.asset != null)
    {
    //从字典移除,通过api卸载资源
    resDic.Remove(resName);
    Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);
    }
    //资源正在异步加载中
    else
    {
    resInfo.isDel = true; //改变标识,代表待移除
    }
    }
    }

    private IEnumerator ReallyLoadAsync(string path, Type type)
    {
    //异步加载资源
    ResourceRequest req = Resources.LoadAsync(path, type);
    //等待资源加载结束后,才会继续执行yield return后面的代码
    yield return req;
    string resName = $"{path}_{type.Name}";
    //资源加载结束,将资源传到外部的委托函数去进行调用
    if (resDic.ContainsKey(resName))
    {
    ResInfo<UnityEngine.Object> resInfo = resDic[resName] as ResInfo<UnityEngine.Object>; //取出资源消息
    resInfo.asset = req.asset; //将资源记录到资源信息内
    //如果发现需要删除,再去移除资源
    if (resInfo.isDel)
    {
    UnloadAsset(path, type);
    }
    else
    {
    resInfo.callBack?.Invoke(resInfo.asset); //将加载出来的资源传递出去
    //加载完毕后,这些引用就可以清空,避免引用的占用带来的内存泄露问题
    resInfo.callBack = null;
    resInfo.coroutine = null;
    }
    }
    }

存在的问题

  1. 在卸载资源时,我们并不知道是否还有地方使用着该资源
  2. UnloadUnusedAssets​ 是卸载没有使用的资源,我们无法判断是否使用

具体代码

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
266
267
268
269
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 资源消息基类
/// </summary>
public abstract class ResInfoBase { }

/// <summary>
/// 资源消息对象 主要用于存储资源消息,异步加载委托消息,异步加载协程消息
/// </summary>
/// <typeparam name="T"></typeparam>
public class ResInfo<T> : ResInfoBase
{
public T asset; //资源
public UnityAction<T> callBack; //用于异步资源加载完后 传递资源到外部的委托
public Coroutine coroutine; //用于异步加载时 开启的协同程序
public bool isDel; //是否需要移除
}

/// <summary>
/// Resources 资源加载模块管理器
/// </summary>
public class ResManager : BaseManager<ResManager>
{
//主要用于存储加载过的资源或者加载中的资源的容器
private Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();

private ResManager() { }

/// <summary>
/// 同步加载资源的方法
/// </summary>
/// <typeparam name="T">资源类型</typeparam>
/// <param name="path"></param>
/// <returns></returns>
public T Load<T>(string path) where T : UnityEngine.Object
{
//资源的唯一ID,通过路径名_资源类型拼接而成
string resName = $"{path}_{typeof(T).Name}";
ResInfo<T> info;
//字典中不存在资源时
if (!resDic.ContainsKey(resName))
{
//直接同步加载,并且记录资源消息到字典中,方便下次直接取出来用
T res = Resources.Load<T>(path);
info = new ResInfo<T>();
info.asset = res;
resDic.Add(resName, info);
return res;
}
else
{
//取出字典的记录
info = resDic[resName] as ResInfo<T>;
//存在异步加载且还在加载中
if (info.asset == null)
{
//停止异步加载,直接采用同步加载的方式加载,并记录
MonoManager.Instance.StopCoroutine(info.coroutine);
T res = Resources.Load<T>(path);
info.asset = res;
//将同步加载出来的内容传递到已存在的回调函数内执行
info.callBack?.Invoke(res);
//执行结束回调,将记录的回调和协程清空,避免内存泄露
info.callBack = null;
info.coroutine = null;
return res;
}
//已经加载过资源
else
{
return info.asset;
}
}
}

/// <summary>
/// 异步加载资源的方法
/// </summary>
/// <typeparam name="T">资源类型</typeparam>
/// <param name="path">资源路径(Resources下的)</param>
/// <param name="callBack">加载结束后的回调函数</param>
public void LoadAsync<T>(string path, UnityAction<T> callBack) where T : UnityEngine.Object
{
//资源的唯一ID,通过路径名_资源类型拼接而成
string resName = $"{path}_{typeof(T).Name}";
ResInfo<T> info;
//不存在消息记录时,说明资源未加载过
if (!resDic.ContainsKey(resName))
{
info = new ResInfo<T>(); //声明一个 资源信息对象
resDic.Add(resName, info); //将资源记录添加到资源内(资源没有加载成功)
info.callBack += callBack; //记录传入的委托函数,一会加载完成了再使用
//通过协同程序去异步加载资源,并记录该协同程序
info.coroutine = MonoManager.Instance.StartCoroutine(ReallyLoadAsync<T>(path));
}
//存在消息记录时,资源已经加载过
else
{
info = resDic[resName] as ResInfo<T>; //从字典中取出资源信息
//资源尚未加载成功
if (info.asset == null)
info.callBack += callBack; //将回调函数添加到委托内,等待加载完毕后一起执行
//资源已经加载完毕
else
callBack?.Invoke(info.asset); //直接执行传入的回调函数
}
}

private IEnumerator ReallyLoadAsync<T>(string path) where T : UnityEngine.Object
{
//异步加载资源
ResourceRequest req = Resources.LoadAsync<T>(path);
//等待资源加载结束后,才会继续执行yield return后面的代码
yield return req;
string resName = $"{path}_{typeof(T).Name}";
//资源加载结束,将资源传到外部的委托函数去进行调用
if (resDic.ContainsKey(resName))
{
ResInfo<T> resInfo = resDic[resName] as ResInfo<T>; //取出资源消息
resInfo.asset = req.asset as T; //将资源记录到资源信息内
//如果发现需要删除,再去移除资源
if (resInfo.isDel)
{
UnloadAsset<T>(path);
}
else
{
resInfo.callBack?.Invoke(resInfo.asset); //将加载出来的资源传递出去
//加载完毕后,这些引用就可以清空,避免引用的占用带来的内存泄露问题
resInfo.callBack = null;
resInfo.coroutine = null;
}
}
}

/// <summary>
/// 异步加载资源的方法
/// </summary>
/// <param name="path">资源路径(Resources下的)</param>
/// <param name="type">资源类型</param>
/// <param name="callBack">加载结束后的回调函数</param>
[Obsolete("注意:建议使用泛型方法,如果需要使用Type加载,请不要和泛型加载混用去加载同类型同名资源")]
public void LoadAsync(string path, Type type, UnityAction<UnityEngine.Object> callBack)
{
//资源的唯一ID,通过路径名_资源类型拼接而成
string resName = $"{path}_{type.Name}";
ResInfo<UnityEngine.Object> info;
//不存在消息记录时,说明资源未加载过
if (!resDic.ContainsKey(resName))
{
info = new ResInfo<UnityEngine.Object>(); //声明一个 资源信息对象
resDic.Add(resName, info); //将资源记录添加到资源内(资源没有加载成功)
info.callBack += callBack; //记录传入的委托函数,一会加载完成了再使用
//通过协同程序去异步加载资源,并记录该协同程序
info.coroutine = MonoManager.Instance.StartCoroutine(ReallyLoadAsync(path, type));
}
//存在消息记录时,资源已经加载过
else
{
info = resDic[resName] as ResInfo<UnityEngine.Object>; //从字典中取出资源信息
//资源尚未加载成功
if (info.asset == null)
info.callBack += callBack; //将回调函数添加到委托内,等待加载完毕后一起执行
//资源已经加载完毕
else
callBack?.Invoke(info.asset); //直接执行传入的回调函数
}
}

private IEnumerator ReallyLoadAsync(string path, Type type)
{
//异步加载资源
ResourceRequest req = Resources.LoadAsync(path, type);
//等待资源加载结束后,才会继续执行yield return后面的代码
yield return req;
string resName = $"{path}_{type.Name}";
//资源加载结束,将资源传到外部的委托函数去进行调用
if (resDic.ContainsKey(resName))
{
ResInfo<UnityEngine.Object> resInfo = resDic[resName] as ResInfo<UnityEngine.Object>; //取出资源消息
resInfo.asset = req.asset; //将资源记录到资源信息内
//如果发现需要删除,再去移除资源
if (resInfo.isDel)
{
UnloadAsset(path, type);
}
else
{
resInfo.callBack?.Invoke(resInfo.asset); //将加载出来的资源传递出去
//加载完毕后,这些引用就可以清空,避免引用的占用带来的内存泄露问题
resInfo.callBack = null;
resInfo.coroutine = null;
}
}
}

/// <summary>
/// 指定卸载一个资源
/// </summary>
/// <param name="path">要卸载的资源的路径</param>
public void UnloadAsset<T>(string path)
{
string resName = $"{path}_{typeof(T).Name}";
//是否存在对应资源
if (resDic.ContainsKey(resName))
{
ResInfo<T> resInfo = resDic[resName] as ResInfo<T>;
//资源已经加载结束
if (resInfo.asset != null)
{
//从字典移除,通过api卸载资源
resDic.Remove(resName);
Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);
}
//资源正在异步加载中
else
{
resInfo.isDel = true; //改变标识,代表待移除
}
}
}

/// <summary>
/// 指定卸载一个资源
/// </summary>
/// <param name="path">要卸载的资源的路径</param>
public void UnloadAsset(string path, Type type)
{
string resName = $"{path}_{type.Name}";
//是否存在对应资源
if (resDic.ContainsKey(resName))
{
ResInfo<UnityEngine.Object> resInfo = resDic[resName] as ResInfo<UnityEngine.Object>;
//资源已经加载结束
if (resInfo.asset != null)
{
//从字典移除,通过api卸载资源
resDic.Remove(resName);
Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);
}
//资源正在异步加载中
else
{
resInfo.isDel = true; //改变标识,代表待移除
}
}
}

/// <summary>
/// 异步卸载对应没有使用的Resources相关资源
/// </summary>
/// <param name="callBack">回调函数</param>
public void UnloadUnusedAssets(UnityAction callBack)
{
MonoManager.Instance.StartCoroutine(ReallyUnloadUnusedAssets(callBack));
}

private IEnumerator ReallyUnloadUnusedAssets(UnityAction callBack)
{
AsyncOperation ao = Resources.UnloadUnusedAssets();
yield return ao;
callBack?.Invoke();
}
}