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 几次
相关 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}`); }
静态加载: 多个预制引用同一张图
-
制作了两个最简单的预制, 里面引用同一个图片
-
运行动态 load 出来
-
查看一下这两个预制路径的资源信息
-
发现 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
-
销毁预制时引用计数 -1
-
未加载 预制情况下, 总数量是 55
-
加载 预制情况下, 总数量是 60
-
销毁预制, 并且预制引用计数-1, 总数量回到 55
资源正常
动态加载: 正确
-
未加载 预制情况下, 总数量是 55
-
加载预制后, 在动态加载一张 big002 的图片并且引用计数+1, 总数 63
-
销毁预制, 并且预制引用计数-1, 并且将动态加载出来的资源引用计数-1, 总数量回到 55
资源正常!!!这里很关键的就是要自己去维护 动态加载的资源的 引用计数!!!
动态加载: 错误
-
未加载 预制情况下, 总数量是 55
-
加载预制后, 在动态加载一张 big002 的图片 (不操作引用计数), 总数 63
-
销毁预制, 总数量没有回到 55
资源不正常看到 big002 spriteFrame 引用计数虽然为 0, 但是为什么还没释放呢? 导致关联的 texture2d 和 imageAsset 也没有被释放
cocos 触发是放是由 decRef 减引用计数的方法里触发, 所有还是需要手动调用, 才能进行释放
-
3.7.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) }) } }
-
-
相关加载接口示例
// ------------------------------------ 对外接口 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
实测
-
未加载 预制情况下, 总数量是 55, 内存和图片缓存如下
-
加载 预制 并且动态加载 图片 情况下, 总数量是 61, 内存和图片缓存如下
-
销毁预制节点, 总数量回到 55, 内存和图片缓存也回到了初始值, 说明正常释放资源