cocos2d 释放资源的最佳时机

有场景A跟场景B,
场景A是当前场景,场景B是将要替换的新场景。

那么A场景的资源释放最佳时机是在什么时候呢?

这是释放资源的代码(注意要按这个顺序释放):

CCAnimationCache::purgeSharedAnimationCache();
SpriteFrameCache::getInstance()->removeUnusedSpriteFrame();
CCTextureCache::getInstance()->removeUnusedTextures();
CCTextureCache::getInstance()->getCachedTextureInfo();
```



1.
最开始我想到的,应该是在场景A的onExit里释放,试验了下,资源并没有被释放,说明当前纹理仍然在
被引用,即场景A的子节点并未remove。
后来查了下,发现每个场景的资源释放都是在一个叫dealloc的方法里,而这个方法是在onExit之后执行的。
也就是说在onExit里释放资源是不合适的做法。


2.
我突然想起来,每个类不都有个析构函数么?析构函数是在该类的对象被delete时调用的,
只要在A场景的析构函数里释放不就可以了?试验了下,发现也没效果,
仔细想想,我创建场景用的方法是Cocos2DX推荐的默认方法,即场景类继承一个Layer,
然后再createScene里创建一个Scene,再把当前类添加到Scene里。替换场景后,
该Layer被释放时,场景中的其他资源并不一定被释放,所以此方法也是行不通的。


3.
竟然场景真正remove子节点是在dealloc方法里,那么在dealloc里释放资源不就好?
可惜的是我根本就找不到有这个方法。于是放弃了。
后来百度了下场景的调用顺序规则:
replaceScene : SceneB
init : SceneB
onExitTransitionDidStart : SceneA
onExit : SceneA
dealloc : SceneA
onEnter : SceneB
onEnterTransitionDidFinish : SceneB
```

由此可看出,B场景的onEnter函数在A场景的dealloc之后执行。
而且
那么只要在B场景的onEnter里释放A场景的资源就可以。
试验了下,资源的确被释放了。

而且,释放资源是在B场景的init之后执行,这样的好处就是,假设场景B中用到场景A中的某些资源,
这些资源就不会被释放再加载,造成不必要的内存高峰。


4.
问题到此结束了吗?我也以为结束了,然后又发现新问题。
一旦我给场景过渡加上效果,以上方法就不灵验了,释放失败。
Director::getInstance()->replaceScene(CCTransitionFade::create(0.5f, SceneB::createScene()));
```

然后调试跟踪下执行顺序,发现问题所在了。
当有过渡效果存在的时候,执行顺序变成这样:
replaceScene : SceneB
init : SceneB
onExitTransitionDidStart : SceneA
onEnter : SceneB
onExit : SceneA
onEnterTransitionDidFinish : SceneB
```

没错,场景A并不会立刻结束,而是等到动画效果完毕后才结束。
但是我发现不管有没有使用过渡动画,最后执行的总是onEnterTransitionDidFinish,
那么我们只要将资源释放放在这里就行了。
经过试验,发现又失败了。。。
为啥,因为虽然dealloc方法是在onExit之后执行,但并不是紧追其后,也就是说,
有可能在onEnterTransitionDidFinish 之前,也有可能在其后,因此我们直接在
onEnterTransitionDidFinish 里释放资源并不一定可行。那该怎么解决?
这里我使用了延时释放的方法,在onEnterTransitionDidFinish 执行后过一段时间,
再调用释放资源的方法,这样就可以确保资源被释放。
scheduleOnce(CC_SCHEDULE_SELECTOR(SceneB::release), 2.0f);
```




5.
以上方法的确是可以将资源释放掉,但是不是我们所要的”最佳时机”呢?
仔细观察,B场景的init是在A场景的释放之前的,也就是说,在B场景诞生,A场景彻底释放
的短时间内,会存在A,B场景的所有资源共存的现在。这个时候内存达到巅峰。如果A场景
跟B场景的资源所占内存都非常大的时候,或许会造成崩溃。
那么我们应该如何解决?

目前我的解决方法就是在场景过渡的时候增加一个过渡场景,也就是Loading场景,Loading场景
本身所需的资源并不多,在A场景资源释放完毕后,再开启B场景的资源加载。

:867:感谢分享

我的做法是A切换到loading场景后延迟释放的,然后再跳转到B
但是手机上看中间会有一小段黑屏时间,所以在切换前将A全屏截图添加到loading场景里面

在新场景onEnter过后一帧释放。

顶,确实有帮助,虽然我现在也这么做的,哈哈

不不不,你仔细看我上面第四条,如果有场景过渡动画的话,onEnter下一帧并不是场景A的dealloc过程。
场景A的dealloc要等到动画执行完毕之后再调用。

写的很详细,Mark

我是在场景A的onExit()里面释放removeAllTextures().
然后在loadingScene的onEnter()里面加载场景B的资源

我不知道楼主说:“在场景A的onExit里释放,试验了下,资源并没有被释”,怎么看出资源没有被释放?

你打开任务管理器,观察进程的内存变化就知道了。

任务管理器是没有变化,但是我放在loading场景的onEnter()里面释放A场景的资源,任务管理器一样没有变化?

我尝试了一下放在loading场景的onEnter()或者放在onEnterTransitionDidFinish()里面释放A场景的资源,任务管理器查看的内存一样没有变化?

而且我用getCachedTextureInfo();这个方法查看了 释放后返回的是TextureCache dumpDebugInfo: 0 textures, for 0 KB (0.00 MB),释放前是有资源路径以及大小的.
就是任务管理器的内存没有减小.是怎么回事

最安全的是在onEnterTransitionDidFinish里释放,并且需要延时释放,
因为我第四条说了,如果你的场景有过渡效果的话,在loading的onEnter里释放时没效果的。

你有延时释放吗?因为在onEnterTransitionDidFinish执行时A场景的释放函数并不一定执行了,在onEnterTransitionDidFinish过后一帧,A场景的资源就肯定释放了。

void onEnterTransitionDidFinish(){
scheduleOnce(CC_SCHEDULE_SELECTOR(SceneB::release), 2.0f);
}

void release()
{
CCAnimationCache::purgeSharedAnimationCache();
SpriteFrameCache::getInstance()->removeUnusedSpriteFrame();
CCTextureCache::getInstance()->removeUnusedTextures();
CCTextureCache::getInstance()->getCachedTextureInfo();
}


```

我在onEnterTransitionDidFinish()用了延时代码如下,内存就是没有减少,然道我用cocos2dx 3.6不行吗?

void LoadingScene::onEnterTransitionDidFinish()
{
    log("lodingscene onEnterTransitionDidFinish");
    this->scheduleOnce(schedule_selector(LoadingScene::MutUpdate), 2.0f);
}


void LoadingScene::MutUpdate(float dt)
{
        //此处清除缓存
    SpriteFrameCache::getInstance()->removeUnusedSpriteFrames();
    Director::getInstance()->getTextureCache()->removeAllTextures();

    //此处加载场景B的资源
    loadSceneBdata();//加载配置场景B的资源

}


```

为什么要释放。。内存充足就不要放

你怎么知道你什么时候内存充足。
不要用了就及时释放掉,不然后面越堆越多。

刚刚我再试了下,发现还是有释放的。看你的代码,我发现你应该是在写onEnterTransitionDidFinish时出错了。
你那种写法是直接把父类里的onEnterTransitionDidFinish方法给覆盖了,而不是续写。
正确的写法应该是:

void LoadingScene::onEnterTransitionDidFinish()
{
    Layer::onEnterTransitionDidFinish();           // 续写父类方法需要将父类的方法引入,不然就变成覆盖了
    log("lodingscene onEnterTransitionDidFinish");
    this->scheduleOnce(schedule_selector(LoadingScene::MutUpdate), 2.0f);
}
 
 
void LoadingScene::MutUpdate(float dt)
{
        //此处清除缓存
    SpriteFrameCache::getInstance()->removeUnusedSpriteFrames();
    Director::getInstance()->getTextureCache()->removeAllTextures();
 
    //此处加载场景B的资源
    loadSceneBdata();//加载配置场景B的资源
 
}


```



参考下我的代码。
如果你直接复写父类的方法而不是续写的话,那原有的功能会失效,会导致scheduleOnce方法无法执行,也就是无法执行你的释放函数。




结果:

加了Layer::onEnterTransitionDidFinish(); 还是没用,
CCTextureCache::getInstance()->getCachedTextureInfo();释放后在这里看是显示TextureCache dumpDebugInfo: 0 textures, for 0 KB (0.00 MB),
但是任务管理器里面内存就是没有减少
算了 ,不管了 反正也不是什么大型的游戏.

好机智的选择:+1:

楼主有想过当项目比较大时,有可能会几个场景跳转到同一个场景,这样的话,在下一个场景释放上一个场景的资源是比较麻烦的 还是谢谢楼主,讲解的比较详细