Addressables资源无法卸载问题:两种实现方案的差异分析
Addressables资源卸载异常问题排查
在测试Unity Addressables资源加载与卸载逻辑时,出现两种不同结果:
- 先卸载旧资源再加载新资源的实现,Profiler显示资源引用数为0,状态为
none,卸载正常。 - 先加载新资源完成后再卸载旧资源的实现,Profiler显示资源引用数为0,但状态仍为
active,资源未完成卸载。
正常卸载的实现代码
[SerializeField] private Image _image; [SerializeField] private AssetReferenceSprite[] _sprites; private int _index; private AsyncOperationHandle<Sprite> _spriteLoadOperation; private void Update() { if (Input.GetKeyDown(KeyCode.Alpha1)) { SetNextSprite(); } } private void SetNextSprite() { _index = (_index + 1) % _sprites.Length; if (_spriteLoadOperation.IsValid()) { _spriteLoadOperation.Completed -= OnLoadSprite; _spriteLoadOperation.Release(); } _spriteLoadOperation = _sprites[_index].LoadAssetAsync(); _spriteLoadOperation.Completed += OnLoadSprite; } private void OnLoadSprite(AsyncOperationHandle<Sprite> handle) { _image.sprite = handle.Result; }
无法正常卸载的实现代码
[SerializeField] private Image _image; [SerializeField] private AssetReferenceSprite[] _sprites; private int _index; private AsyncOperationHandle<Sprite> _spriteLoadOperation; private AsyncOperationHandle<Sprite> _previousSpriteLoadOperation; private void Update() { if (Input.GetKeyDown(KeyCode.Alpha1)) { SetNextSprite(); } } private void SetNextSprite() { _index = (_index + 1) % _sprites.Length; if (_spriteLoadOperation.IsValid()) { _spriteLoadOperation.Completed -= OnLoadSprite; } _spriteLoadOperation = _sprites[_index].LoadAssetAsync(); _spriteLoadOperation.Completed += OnLoadSprite; } private void OnLoadSprite(AsyncOperationHandle<Sprite> handle) { _image.sprite = handle.Result; if (_previousSpriteLoadOperation.IsValid()) { _previousSpriteLoadOperation.Release(); } _previousSpriteLoadOperation = handle; }
问题原因
- 未释放旧的加载操作句柄:第二种实现的
SetNextSprite方法中,仅移除了旧_spriteLoadOperation的Completed事件,但未调用Release()。若旧加载操作仍在进行中,该句柄会持续持有资源引用,导致Addressables内部资源引用计数无法归零,资源状态保持active。 - 句柄管理逻辑漏洞:
_previousSpriteLoadOperation仅记录上一次加载完成的句柄,忽略了中途被替换的未完成加载句柄,这些未被释放的句柄会成为资源的隐性引用。
修复方案
如果要保留“加载新资源完成后再卸载旧资源”的逻辑,可通过以下方式完善句柄管理:
方案1:补全旧加载句柄的释放
修改SetNextSprite方法,在替换新句柄前释放旧句柄:
private void SetNextSprite() { _index = (_index + 1) % _sprites.Length; if (_spriteLoadOperation.IsValid()) { _spriteLoadOperation.Completed -= OnLoadSprite; _spriteLoadOperation.Release(); // 新增:释放旧加载句柄 } _spriteLoadOperation = _sprites[_index].LoadAssetAsync(); _spriteLoadOperation.Completed += OnLoadSprite; }
方案2:异步加载确保顺序(推荐)
使用异步等待确保新资源加载完成后再处理旧资源,避免未完成加载的句柄残留:
[SerializeField] private Image _image; [SerializeField] private AssetReferenceSprite[] _sprites; private int _index; private AsyncOperationHandle<Sprite> _currentHandle; private void Update() { if (Input.GetKeyDown(KeyCode.Alpha1)) { LoadNextSprite(); } } private async void LoadNextSprite() { _index = (_index + 1) % _sprites.Length; // 加载新资源并等待完成 var newHandle = _sprites[_index].LoadAssetAsync(); await newHandle.Task; // 替换Sprite,切断旧资源的Unity对象引用 _image.sprite = newHandle.Result; // 释放旧资源的句柄 if (_currentHandle.IsValid()) { _currentHandle.Release(); } // 更新当前句柄 _currentHandle = newHandle; // 可选:强制回收未使用资源,加速卸载 Resources.UnloadUnusedAssets(); GC.Collect(); }
补充说明
- Addressables的
Release()仅减少内部引用计数,计数归零后资源会被标记为可卸载,但实际卸载需等待Unity自动GC或手动调用Resources.UnloadUnusedAssets()。 - 若存在快速连续切换资源的场景,务必确保所有未完成的加载句柄都被正确释放,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Mariya
相关产品推荐
相关产品推荐

