分享一个极简的资源释放解决方案

本文实现基于Cocos Creator 3D v1.2.0
Creator 3D v1.2.0 demo:https://forum.cocos.org/uploads/short-url/i3qOaa5QFBwVXuLSba7Ru2WQJkt.rar Cocos v2.2.2 demo ResCleaner-cocos-v222.zip|attachment (446.5 KB)

场景的资源自动释放是有问题的,官方已经修复了。补丁地址:<https://github.com/cocos-creator/engine/pull/7619 >

特点

  • 使用简单,无管理负担。
  • 项目接入成本低,只需要处理对象池部分即可。
  • 性能消耗跟场景上的节点数成正比。

原理


释放逻辑如下:

  1. 遍历场景上所有节点,搜集每个节点上所有组件引用的资源。
  2. 收集所有正在加载中的资源(loader.loadRes、loader.loadResArray)
  3. 遍历资源缓存(loader._cache)。如果当前资源既没有被场景节点引用又不属于加载中则释放。

代码讲解

核心代码如下:

android SkinAppCompatDelegateImpl 释放资源_对象池

另外,代码中有部分函数是从引擎源码(auto-release-utils.ts)中拷贝出来的,因为引擎没有对外暴露这些函数。

android SkinAppCompatDelegateImpl 释放资源_加载_02

难点在于加载中资源的剔除。大致逻辑如下:首先获取所有加载中的加载队列。然后对加载队列依赖的资源进行递归资源引用查找。

项目接入

  1. 引入脚本文件ResCleaner.ts到项目当中
  2. 检查项目所有使用对象池的地方。如果完全没有使用到对象池直接跳到step4。否则执行step3.
  3. 由于引擎内置的NodePool在节点回收的时候会从场景上移除,导致节点无法被资源清理函数遍历到。所以需要自己开发一个对象池。新实现的对象池需要利用node.active = false来避免使用node.removeFromParent()。样例如下:
    直接使用一个数组存储相同预制体
  4. android SkinAppCompatDelegateImpl 释放资源_对象池_03

  5. 调用资源清理函数 ResCleaner.clean()。调用时机建议在场景切换之后,也可以在收到
    内存不足告警之后调用。

释放效果演示

测试代码如下:

android SkinAppCompatDelegateImpl 释放资源_加载_04

执行流程:
在start里面开始加载预制体
加载完成后实例化成节点并添加到场景上
调用资源清理函数
将节点移出场景并销毁
调用资源清理函数
将成员变量spframe对资源的引用置空
再次调用资源清理函数

MainCity场景初始状态

android SkinAppCompatDelegateImpl 释放资源_加载_05

spframe初始资源引用

android SkinAppCompatDelegateImpl 释放资源_加载_06

prefabA内容

android SkinAppCompatDelegateImpl 释放资源_cocos-creator_07

打印的日志如下:

android SkinAppCompatDelegateImpl 释放资源_cocos-creator_08

代码和日志结合起来看

android SkinAppCompatDelegateImpl 释放资源_cocos-creator_09

图解一下整个过程:

初始

android SkinAppCompatDelegateImpl 释放资源_对象池_10

预制体加载完成成

android SkinAppCompatDelegateImpl 释放资源_对象池_11

实例化成节点并添加到场景上

android SkinAppCompatDelegateImpl 释放资源_实例化_12

调用资源清理后

android SkinAppCompatDelegateImpl 释放资源_cocos-creator_13

节点销毁后

android SkinAppCompatDelegateImpl 释放资源_加载_14

调用资源清理后

android SkinAppCompatDelegateImpl 释放资源_cocos_15

调用 this.spframe = null

android SkinAppCompatDelegateImpl 释放资源_加载_16

调用资源清理后

android SkinAppCompatDelegateImpl 释放资源_加载_17

注意事项

  • 节点如果要复用请使用node.active = false替代node.removeFromParent()。
  • 以下几种情况资源引用不会被统计到
  1. 使用非组件脚本引用资源。如果类不是继承自Component,就无法挂在节点上,也就无法被统计到。
  2. 在组件脚本中间接引用资源。出于性能考虑不支持。
  3. 使用组件的静态成员变量引用资源。感觉用的不多所以暂不支持。