creator-泄漏检测之资源篇


title: creator-泄漏检测之资源篇
categories: Cocos2dx
tags: [creator, 优化, 泄漏, 内存]
date: 2023-03-29 14:48:48
comments: false
mathjax: true
toc: true

creator-泄漏检测之资源篇


前篇

  • 资源释放 - https://docs.cocos.com/creator/manual/zh/asset/release-manager.html

  • 执行相关测试打快照前, 一定要多 gc js 几次

    image-20230329152312826


相关 dump 信息的代码

  • dump

    public dumpPath(path: string) {
          
          
        let bundleName = "resources"
    
        let bundle = assetManager.getBundle(bundleName);
        let asset01 = bundle.get(path);
        let uuid = asset01._uuid
    
        this.dumpUuidRecur(uuid)
    }
    
    private dumpUuidRecur(uuid: string, depth: number = 1) {
          
          
        let pre = "---".repeat(depth)
        var asset01 = assetManager.assets.get(uuid)!;
    
        var uuids: string[] = assetManager.dependUtil.getDepsRecursively(uuid)!;
        LogUtil.D(`${
            
            pre} dumpUuidRecur: ${
            
            uuid}, refCnt: ${
            
            asset01.refCount}\nasset:`, asset01, "\nuuid:", uuids)
    
        uuids.forEach(uuid => {
          
          
            this.dumpUuidRecur(uuid, ++depth)
        });
    }
    
    public dump() {
          
          
        assetManager.assets.forEach((value: Asset, key: string) => {
          
          
            console.log(assetManager.assets.get(key));
        })
        console.log(`当前资源总数:${
            
            assetManager.assets.count}`);
    }
    
    
    
    

静态加载: 多个预制引用同一张图

  1. 制作了两个最简单的预制, 里面引用同一个图片

    image-20230329145021401

  2. 运行动态 load 出来

  3. 查看一下这两个预制路径的资源信息

    image-20230329145353550

    • 发现 spriteFrame 就有维护引用计数, texture2d 和 imageAsset 就没维护引用计数

      根据管饭文档: https://docs.cocos.com/creator/manual/zh/asset/release-manager.html#%E8%B5%84%E6%BA%90%E7%9A%84%E9%9D%99%E6%80%81%E5%BC%95%E7%94%A8

      我们只需要维护 预制的引用计数 -1, 引擎会去维护 spriteFrame 引用技术, 进而维护 texture2d 和 imageAsset, spriteFrame 就相当于右图中的 Material

      image-20230329145540060


销毁预制时引用计数 -1

  1. 未加载 预制情况下, 总数量是 55

    image-20230329145900957

  2. 加载 预制情况下, 总数量是 60

    image-20230329145917606

  3. 销毁预制, 并且预制引用计数-1, 总数量回到 55
    资源正常

    image-20230329145937661


动态加载: 正确

  1. 未加载 预制情况下, 总数量是 55

    image-20230329150045997

  2. 加载预制后, 在动态加载一张 big002 的图片并且引用计数+1, 总数 63

    image-20230329150122949

  3. 销毁预制, 并且预制引用计数-1, 并且将动态加载出来的资源引用计数-1, 总数量回到 55
    资源正常

    !!!这里很关键的就是要自己去维护 动态加载的资源的 引用计数!!!

    image-20230329150147216


动态加载: 错误

  1. 未加载 预制情况下, 总数量是 55

    image-20230329150301368

  2. 加载预制后, 在动态加载一张 big002 的图片 (不操作引用计数), 总数 63

    image-20230329150322070

  3. 销毁预制, 总数量没有回到 55
    资源不正常

    image-20230329150345262

    看到 big002 spriteFrame 引用计数虽然为 0, 但是为什么还没释放呢? 导致关联的 texture2d 和 imageAsset 也没有被释放

    cocos 触发是放是由 decRef 减引用计数的方法里触发, 所有还是需要手动调用, 才能进行释放

    • 3.7.1 相关源码

      image-20230329150427482


资源追踪自动维护引用计数

摸清了释放规则后, 就可以在自定义的资源管理器中, 加入一个 追踪器, 让加载出来的资源, 根据 节点 的生命周期结束时, 自动去维护相关加载出来的资源, 让开发人员不用去关心引用计数问题, 只关心节点的生成和销毁

  1. 其实原理很简单, 就是在加载出来时往 节点 身上挂个组件去记录资源, 从而维护引用计数.

    • AssetTracker.ts

      import {
              
               _decorator, Component, Node, Asset, SpriteFrame } from 'cc';
      import {
              
               LogUtil } from '../log/LogUtil';
      const {
              
               ccclass, property } = _decorator;
      
      @ccclass('AssetTracker')
      export class AssetTracker extends Component {
              
              
      
          public static trace(go: Node, ast: Asset) {
              
              
              let at = go.getComponent(AssetTracker)
              if (!at) {
              
              
                  at = go.addComponent(AssetTracker)
              }
              at.traceInner(ast)
          }
      
          private _astArr = new Array<Asset>()
      
          traceInner(ast: Asset) {
              
              
              ast.addRef()
              this._astArr.push(ast)
          }
      
          onDestroy() {
              
              
              // LogUtil.D(`--- onDestroy, cnt: ${this._astArr.length}, _astArr:\n`, this._astArr)
              this._astArr.forEach((ast, idx, arr) => {
              
              
                  ast.decRef()
              })
              this._astArr = null
          }
      
          debugDump() {
              
              
              this._astArr.forEach((ast, idx, arr) => {
              
              
                  LogUtil.D("", ast)
              })
          }
      }
      
  2. 相关加载接口示例

    // ------------------------------------ 对外接口 begin
    public async instantiateAsync(prefabPath: string, parent?: Node) {
          
          
        return new Promise<Node>((resolve) => {
          
          
            this.load(prefabPath, (err: Error, asset: Prefab) => {
          
          
                if (err) {
          
          
                    LogUtil.E(`--- instantiateAsync error, path: ${
            
            prefabPath}, err:`, err)
                    resolve(null)
                    return
                }
    
                let go = instantiate(asset);
                AssetTracker.trace(go, asset) // 资源计数追踪
                if (parent)
                    go.parent = parent
                resolve(go)
            })
        })
    }
    
    // refGo 挂点, 最好是资源要依附的节点
    public async loadAssetAsync<T extends Asset>(assetPath: string, refGo: Node) {
          
          
        return new Promise<T>((resolve) => {
          
          
            this.load(assetPath, (err: Error, asset: T) => {
          
          
                if (err) {
          
          
                    LogUtil.E(`--- loadAssetAsync error, path: ${
            
            assetPath}, err:`, err)
                    resolve(null)
                    return
                }
    
                AssetTracker.trace(refGo, asset) // 资源计数追踪
                resolve(asset)
            })
        })
    }
    
    // refGo 挂点, 最好是资源要依附的节点
    public async loadRemoteAsync<T extends Asset>(url: string, opts: IRemoteOptions, refGo: Node) {
          
          
        return new Promise<T>((resolve) => {
          
          
            this.loadRemote(url, opts, (err: Error, asset: T) => {
          
          
                if (err) {
          
          
                    LogUtil.E(`--- loadRemoteAsync error, url: ${
            
            url}, err:`, err)
                    resolve(null)
                    return
                }
    
                AssetTracker.trace(refGo, asset) // 资源计数追踪
                resolve(asset)
            })
        })
    }
    // ------------------------------------ 对外接口 end
    

实测

  1. 未加载 预制情况下, 总数量是 55, 内存和图片缓存如下

    image-20230329152013866

  2. 加载 预制 并且动态加载 图片 情况下, 总数量是 61, 内存和图片缓存如下

    image-20230329152036689

  3. 销毁预制节点, 总数量回到 55, 内存和图片缓存也回到了初始值, 说明正常释放资源

    image-20230329152054504


猜你喜欢

转载自blog.csdn.net/yangxuan0261/article/details/129838344
今日推荐