清空缓存链表

如果我们有一个资源,第一次Release它时我们让它缓存在m_NoReferenceAssetMapList链表中,第二次Release时我们让它彻底清除,这时这个资源ResourceItem对应的Obj引用和AssetBundle引用都会清除,但是ResorceItem本身还是被缓存在m_NoReferenceAssetMapList链表中,我们需要添加清空缓存链表的方法。

首先给ResourceItem添加是否Clear的变量:

1
2
3
4
5
6
7
8
9
10
/// <summary>
/// 用来读取AB配置信息和引用AB,还用来记录AB内部资源的引用信息
/// </summary>
public class ResourceItem
{
//...
//是否清除(跳场景时清除)
public bool m_Clear = true;//++++++++++++++++++++++++++++++++++++++++
//...
}

修改DestroyResourceItem方法,之前我们把AssetDic.Remove放在了m_NoReferenceAssetMapList.InsertToHead前面,这就导致了无论是否完全删除资源,缓存字典都会删除这个资源记录;再这里我们移到后面,这样我们只有完全删除资源时才会删除缓存字典的资源记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
}
}

添加ClearCache方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// <summary>
/// 清空未引用ResItem缓存
/// </summary>
public void ClearCache()
{
//这里使用tempList是因为我们遍历AssetDic时不能调用DestroyResourceItem
//因为后者有对AssetDic的删除操作,字典在遍历时不能增删,只能改查
List<ResourceItem> tempList = new List<ResourceItem>();
foreach (ResourceItem item in AssetDic.Values)
{
if (item.m_Clear)
{
tempList.Add(item);
}
}
tempList.ForEach(item =>
{
DestroyResourceItem(item,true);
});
tempList.Clear();
}

由于我们完全使用AssetDic作为临时缓存,它能明确指出哪些资源是已经缓存的,哪些资源是完全删除的,所以m_NoReferenceAssetMapList链表目前用来缓存资源没有意义了,我们在DestroyResourceItem里面先注释掉添加进链表的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
//...
}

这些只是临时的操作,双向链表肯定是要用到的,否则用不到的资源会一直放在AssetDic里

预加载

预加载就是先同步加载资源,再卸载资源(但是资源卸载参数是false),故意把ReourceItem(包括Object和相应的AssetBundle)缓存在内存里。

资源预加载时,GetCacheResourceItem(crc,0)设置的引用计数为零,这表示预加载的资源随时可以卸载

我们还添加了根据路径卸载资源的ReleaseResource重载方法,这样能省去根据资源卸载时需要遍历缓存字典的麻烦

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
    /// <summary>
/// 资源预加载
/// </summary>
/// <param name="path"></param>
public void PreLoadRes(string path)
{
if (string.IsNullOrEmpty(path))
{
return;
}
uint crc = CRC32.GetCRC32(path);
ResourceItem item = GetCacheResourceItem(crc,0);//引用计数为零
if (item != null)
{
return;
}
Object 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;
}
else
{
obj = item.m_AssetBundle.LoadAsset<Object>(item.m_AssetName);
}
}
}
CacheResource(path, ref item, crc, obj);
//跳场景不清空缓存
item.m_Clear = false;
ReleaseResource(path, false);
}
/// <summary>
/// 不需要实例化的资源卸载,根据路径
/// </summary>
public bool ReleaseResource(string path,bool destroyObj = false)
{
if (string.IsNullOrEmpty(path))
return false;
uint crc = CRC32.GetCRC32(path);
ResourceItem item = null;
if (!AssetDic.TryGetValue(crc, out item) || item == null)
{
Debug.LogError("AssetDic里不存在该资源:" + path + " 可能释放了多次");
}

item.RefCount--;
DestroyResourceItem(item, destroyObj);
return true;
}

预加载演示

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
using UnityEngine;

public class GameStart : MonoBehaviour
{
public AudioSource m_AudioSource;
private AudioClip m_AudioClip;
private void Awake()
{
AssetBundleManager.Instance.LoadAssetBundleConfig();
ResourceManager.Instance.Init(this);//初始化异步加载
}
private void Start()
{
ResourceManager.Instance.PreLoadRes("Assets/GameData/Sounds/menusound.mp3");
}

private void Update()
{
if(Input.GetKeyDown(KeyCode.A))
{
m_AudioSource.Stop();
ResourceManager.Instance.ReleaseResource(m_AudioClip,true);
m_AudioSource.clip = null;
m_AudioClip = null;
}if (Input.GetKeyDown(KeyCode.D))
{
m_AudioClip = ResourceManager.Instance.LoadResource<AudioClip>("Assets/GameData/Sounds/menusound.mp3");
m_AudioSource.clip = m_AudioClip;
m_AudioSource.Play();
}
}
private void OnApplicationQuit()
{
ResourceManager.Instance.ClearCache();
Resources.UnloadUnusedAssets();
}
}

预加载之后,我们再调用ResourceManager.Instance.LoadResource加载资源时,耗时几乎为零,如果我们在Start里将ResourceManager.Instance.PreLoadRes注释掉,直接加载就会有几毫秒的耗时。