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包及其对应的资源都被卸载
按下两次空格键,再按下一次Q键,实例化出来的两个对象的材质都不会丢失,因为引用计数不为0,AB包及其对应的资源都没有被卸载
但如果再按下一次Q键,实例化出来的两个对象的材质都会丢失,因为引用计数归0了,AB包及其对应的资源都被卸载
回顾之前写的资源管理器
我们之前写的资源管理器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包及其对应的资源都被卸载
由于我们自己实现的管理器复用的是一个句柄,且没有实现引用计数
这导致通过管理器去加载,即使加载了多个资源,只要卸载一次就会完全卸载掉内容
如果要避免这种情况,我们就必须要在自己的管理器内实现一个引用计数
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; 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>(); 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); } }; AddressablesInfo info = new AddressablesInfo(handle); resDic.Add(keyName, info); } public void LoadAssetAsync <T >(Addressables.MergeMode mode, Action<T> callBack, params string [] keys ) { 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>>(); 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); } } }; } 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); } } }; AddressablesInfo info = new AddressablesInfo(handle); resDic.Add(keyName, info); } public void LoadAssetAsync <T >(Addressables.MergeMode mode, Action<AsyncOperationHandle<IList<T>>> callBack, params string [] keys ) { 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>>(); resDic[keyName].count++; if (handle.IsDone) { callBack?.Invoke(handle); } else { handle.Completed += (obj) => { if (obj.Status == AsyncOperationStatus.Succeeded) { callBack?.Invoke(obj); } }; } 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); } }; AddressablesInfo info = new AddressablesInfo(handle); resDic.Add(keyName, info); } public void Release <T >(params string [] keys ) { 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 ; 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 ; 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(); } }