prefab被release,重新loadres。资源丢失

  • Creator 版本:2.0.8

  • 目标平台: native

为了减轻内存压力。做到不常用的prefab用了之后就清理。如何正确的处理资源的加载和释放?如何在js层能获取到prefab中资源是被多次引用的?
我自己对已经加载的资源做了引用计数,但是我没有把所有的资源都去预加载。这样就存在,之前使用过的资源被释放了。
@panda

我的策略是这样
1、loadres只在一个地方统一处理
2、loadres时我会将这个asset存到一个数组
3、release一个prefab时先获取这个prefab的依赖资源,如果其中有资源在第二步的数组里有被依赖到就剔除这个资源,然后释放
4、我是1.10.2,由于音频的bug,音频不释放,不然可能引起闪退

你能提供一下你资源管理的脚本么?我是在场景没有切换的情况下释放资源的。我也做了资源管理,目前不知道问题出在哪里。

有空贴给你,我这边的做法都是单场景,所有场景都是以预设加载的

我也是单场景,自己做了封装。android对内存开销不严格,但是ios就不行了。

private _cache: { [key: string]: cc.Asset[] } = {};

public async loadRes<T extends cc.Asset>(path: string,
    type?: typeof cc.Asset,
    category = AssetManager.DEFAULT_KEY,
    loadingProgress?: ILoadingProgress): Promise<T> {
    let promise = new Promise<T>(resolve => {
        cc.loader.loadRes(path, type,
            (completed, total) => {
                if (loadingProgress) {
                    loadingProgress.reportMainProgress(completed / total);
                }
            },
            (error, resource) => {
                if (error)
                    console.log(error);
                resolve(resource);
                if (resource && category)
                    this.putCache(category, resource);
            });
    });
    return promise;
}

private putCache(category: string, asset: cc.Asset) {
    let list = this._cache[category];
    let hasCache = false;
    if (!list) {
        list = [];
        this._cache[category] = list;
    } else {
        for (const cachaAsset of list) {
            if (cachaAsset["_uuid"] == asset["_uuid"]) {
                hasCache = true;
                break;
            }
        }
    }
    if (!hasCache)
        list.push(asset);
}

releasePrefab(prefab: cc.Prefab) {
    this.releaseAssetRecursively(prefab);
}

private releaseAssetRecursively(asset: cc.Asset) {
    this.removeCacheAsset(asset);
    // 音频不能释放,避免 crash
    if (asset instanceof cc.AudioClip) {
        // cc.loader.releaseAsset(asset);
        return;
    }
    
    let deps = cc.loader.getDependsRecursively(asset);
    let total = deps.length;
    // 当前场景依赖的资源不能释放
    let scene = cc.director.getScene();
    let sDeps = scene["dependAssets"];
    for (const cDep of sDeps) {
        let idx = deps.indexOf(cDep);
        if (idx >= 0) {
            deps.splice(idx, 1);
        }
    }

    for (const key in this._cache) {
        if (this._cache.hasOwnProperty(key)) {
            const list = this._cache[key];
            for (let i = 0; i < list.length; i++) {
                let cacheDeps = cc.loader.getDependsRecursively(list[i]);
                for (const cDep of cacheDeps) {
                    let idx = deps.indexOf(cDep);
                    if (idx >= 0) {
                        deps.splice(idx, 1);
                    }
                }
            }
        }
    }
    cc.loader.release(deps);
    console.log(`Release asset [${asset.name}] with ${deps.length}(${total}) dependencies.`);
}

private removeCacheAsset(asset: cc.Asset) {
    for (const key in this._cache) {
        if (this._cache.hasOwnProperty(key)) {
            const list = this._cache[key];
            for (let i = 0; i < list.length; i++) {
                let cacheAsset = list[i];
                if (cacheAsset == asset) {
                    list.splice(i, 1);
                    break;
                }
            }
        }
    }
}

释放的时候,不能把被依赖的资源释放

如果这个prefab中的资源没有被依赖,然后我释放了。再loadres这个prefab会出现黑屏。就是出现资源丢失,是因为cache里这个prefab没有被删除,然后我拿的是cache里的?方便加qq探讨一下么?710260185

是因为图片被你释放了,但是cc.loader里面缓存还在,这时候cc.loader会直接返回cache

这种情况,你怎么处理的?

我有处理这种情况,例如你加载了a预设,在a预设跑的时候又加载了b图片,那么在a释放时要把b也释放了

prefab里只用了一次的静态资源,是不是就没办法释放了。cache没法清啊。

你的代码贴出来看一下

     //资源计数
     /**
     *
     * @param res cc.Texture2D cc.Prefab cc.SpriteAtlas
     */
    resRetain(res){
        let des = cc.loader.getDependsRecursively(res);
        if(des)
        {
            for(let i = 0 ; i < des.length;++i)
            {
                let path = des[i];
                if(!this._resMap[path])this._resMap[path]=1;
                else ++this._resMap[path];
                cclog.d("----path:%s-----count--->%d",path,this._resMap[path]);
            }
        }
    }

    //释放渲染计数为1的
    /**
     *
     * @param resPath cc.Texture2D cc.Prefab cc.SpriteAtlas
     */
    resRelease(resPath)
    {
       let resTag =  cc.loader.getRes(resPath);
       if(!resTag){
           cclog.d("----该资源没有加载过--->",resPath);
           return;
       }
       let reclyList=[];
       let des = cc.loader.getDependsRecursively(resTag);
        for(let i = 0 ; i < des.length;++i)
        {
            let path = des[i];
            let resType = cc.loader.getRes(path);
            if( resType instanceof cc.Texture2D
                || resType instanceof cc.Prefab
                )//|| resType instanceof cc.SpriteAtlas
            {
                if(this._resMap[path] && --this._resMap[path]<=0)
                {
                    reclyList.push(path);
                    delete this._resMap[path];
                }
            }
        }
        reclyList.length>0 && cc.loader.release(reclyList);
    },

我猜你把Textrue2d释放了,但是spriteframe却没有释放,所以黑了

//把缓存中 存储的删掉 也不行。。
let id = cc.loader._getReferenceKey(resTag);
if(cc.loader.getItem(id)){
cc.loader.removeItem(id);
}

不是,resRelease这个方法,你只释放了两种类型的资源,试着把这个判断去掉试试

判断去掉 就出现 prefab 加载失败,prefab 中对象创建失败。 应该是关联的资源被释放造成的。
我查查我这面 关联引用 的资源。