You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

问题原因

  1. 未释放旧的加载操作句柄:第二种实现的SetNextSprite方法中,仅移除了旧_spriteLoadOperation的Completed事件,但未调用Release()。若旧加载操作仍在进行中,该句柄会持续持有资源引用,导致Addressables内部资源引用计数无法归零,资源状态保持active。
  2. 句柄管理逻辑漏洞:_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 10:05:08