CocosCreator 3.7 assetManager.loadRemote 资源异步中过度卸载及管理

最近在用3.7做项目, 碰到问题, 如果A Sprite和 B Sprite 同时加载了, URL_1, 当处于异步状态下, URL_1未完成加载, 这时改变 A Sprite 去加载 URL_2, 当URL_2在URL_1加载前完成设置完成后, URL_1加载完成, 首先会激活 A Sprite 完成回调去设置, 我们可以检测 URL_2是A最后需要加载内容, 自然略过 URL_1的内容, 这样 URL_1 就应该是无效加载, 我们可以判断资源 refCount 当小于1, 释放掉, 这时都没问题, 紧接着 B Sprite 也会加载完毕, 获得回调中的 ImageAsset 其实已经在 A Sprite 流程中被释放, 是一个无效的对象, 导致 B Sprite 无法成功设置.

目前个人解决办法是加载前对 URL 加载内容, 队列数量进行管理, 防止过度卸载

对象销毁才去资源销毁

资源refCount小于1时不要立即销毁,都要加生命周期延迟销毁,再延迟结束销毁的时候再判断一下refCount是不是小于1

之前也加过延迟处理, 也是有概率出问题, 现在在写的是在异步前就进行控制, 异步中继续控制, 异步结束持续控制.
下面的代码是之前出问题的代码

/**
 * 设置远程图片 (监听 sprite 自动回收)
 * that.cacheLoad 在执行的时候行为很乱
 * @param sprite 
 * @param type 
 * @param bundle 
 * @param info 
 * @param loadTime 操作的时间, 用于判断是否是异步
 * @returns 
 */
public setSprite(sprite: Sprite | null | undefined, type: GAssetLoadType, bundle: string, info: string): Promise<Sprite | null> {
    return new Promise(async (resolve) => {
        if (sprite) {
            const doKey2 = `${$g.now} ${Math.random()}`
            let doKey = this._handleKey(sprite)
            const tempURL = type === GAssetLoadType.Remote ? this.asset.killCache(info) : info
            if (sprite.spriteFrame) {
                // 检查图片是否没有变化
                // texture.nativeUrl 用于本机资产的可序列化URL。供内部使用
                const texture = sprite.spriteFrame.packable ? sprite.spriteFrame.original?._texture : sprite.spriteFrame.texture
                if (texture instanceof Texture2D && texture._mipmaps && texture.image?.nativeUrl === tempURL) {
                    return resolve(sprite)
                }
                // 清理老的贴图
                this.clear(sprite)
                doKey = this._handleKey(sprite)
            }
            //下载新资源
            if (info) {
                // const assetPath = that.asset.getAssetPath(type, bundle, info)
                // let cache: { time: number; assetPath: string; } | null = null
                // if (that.cacheLoad.has(sprite.uuid)) {
                //     cache = that.cacheLoad.get(sprite.uuid)!
                //     cache.time = $g.now
                //     cache.assetPath = assetPath
                // } else {
                //     cache = { time: $g.now, assetPath: assetPath }
                //     that.cacheLoad.set(sprite.uuid, cache)
                // }
                let imageAsset: ImageAsset | null = await this.asset.get(type, bundle, info)
                if (imageAsset) {
                    let log!: GAssetManagerLogAssetType
                    let logHandle!: GAssetManagerLogAssetHandleType
                    if ($g.DEBUG) {
                        log = this.asset.assetLogURLAsset(imageAsset.nativeUrl, imageAsset.uuid)
                        log.type = type
                        log.bundle = bundle
                        log.info = info
                        log.refCount = imageAsset.refCount
                        logHandle = {
                            component: sprite.name,
                            path: $c.getNodePath(sprite.node),
                            uuid: `${sprite.uuid} ${isValid(sprite, true)} node:${sprite.node?.uuid ?? ''} url:${imageAsset.nativeUrl}`,
                            refCount: imageAsset.refCount,
                            type: 0,
                            time: $g.now,
                            stack: new Error().stack ?? '',
                            key: sprite[this.HandleKey],
                            keySprite: sprite[this.HandleKey],

                        }
                        logHandle.stack = doKey2
                        log.handle.push(logHandle)
                    }
                    // cache = null
                    // if (that.cacheLoad.has(sprite.uuid)) cache = that.cacheLoad.get(sprite.uuid)!
                    //  && imageAsset.width > 0 && imageAsset.height > 0 过度释放会造成
                    // 异步后图片可能已经被设置了对的图集
                    let reImageAsset: ImageAsset | null = null
                    if (isValid(sprite, true) && sprite[this.HandleKey] === doKey) {
                        // 如果未对本图进行异步操作, 并且突然有图了
                        if (sprite.spriteFrame) {
                            const texture = sprite.spriteFrame.packable ? sprite.spriteFrame.original?._texture : sprite.spriteFrame.texture
                            if (texture instanceof Texture2D && texture._mipmaps && texture.image?.nativeUrl === tempURL) {
                                reImageAsset = texture.image
                            }
                            // 清理老的贴图
                            if (!reImageAsset) {
                                this.clear(sprite)
                                doKey = this._handleKey(sprite)
                                if ($g.DEBUG) {
                                    logHandle.key = doKey
                                    logHandle.keySprite = sprite[this.HandleKey]
                                }
                            }
                        }
                        if (!reImageAsset) {
                            // if (cache && cache.assetPath === assetPath && isValid(sprite, true)) {
                            // that.cacheLoad.delete(sprite.uuid)
                            const texture = new Texture2D()
                            texture.image = imageAsset
                            const spriteFrame: SpriteFrame = new SpriteFrame()
                            spriteFrame.texture = texture
                            sprite.spriteFrame = spriteFrame

                            imageAsset.addRef()
                            texture.addRef()
                            spriteFrame.addRef()

                            if ($g.DEBUG) {
                                log.refCount = imageAsset.refCount
                                logHandle.refCount = imageAsset.refCount
                                logHandle.type = 1
                                // 检查相同 sprite 和 url 下是否有未释放
                                // let oldRef = 0
                                // for (let e = 0; e < log.handle.length; e++) {
                                //     const old = log.handle[e]
                                //     if (old.uuid === logHandle.uuid) oldRef += old.type
                                // }
                                // if (oldRef > 1) {
                                //     $g.logErr('[GSprite]出现未释放元素')
                                // }
                                $g.log(`[GSprite][Ref:${imageAsset.refCount}]`, log)
                            }
                            return resolve(sprite)
                        }
                    }
                    // if (cache) that.cacheLoad.delete(sprite.uuid)
                    // 当A,B 下 url1,A改下url2, 完成url2, 这时A后完成url1(A会释放url1), 然后B完成url2, url2获取内容可能已被A释放
                    // 这里过度释放
                    // await $g.timer.baseSleep(10)
                    if (imageAsset.refCount < 1 && (reImageAsset === null || reImageAsset !== imageAsset)) {
                        $g.log(assetManager.assets)
                        // 这里会出现过度释放, 连续的加载, 删除后后面还会有用
                        switch (type) {
                            case GAssetLoadType.Remote:
                            case GAssetLoadType.Base64:
                                assetManager.releaseAsset(imageAsset)
                                break;
                            case GAssetLoadType.Resources:
                                resources.release(info)
                                break;
                            case GAssetLoadType.Bundle:
                                const b = await this.asset.getBundle(bundle)
                                if (b) b.release(info)
                                break;
                        }
                        if ($g.DEBUG) {
                            log.isDestroy = true
                            logHandle.type = -1
                        }
                        imageAsset.destroy()
                    }
                    if ($g.DEBUG) $g.log(`[GSprite][丢弃][Ref:${log.refCount}]key:${doKey} sKey:${sprite[this.HandleKey]}`, log)
                    return resolve(null)
                }
            }
        }
        resolve(null)
    })
}

一般动态加载的资源就自己写个资源管理器,动态资源是否在引用也是自己做一套引用计数,这样子资源的加载和销毁全部在管理器中进行