Cocos2d-x 内存管理
一、内存管理机制
Cocos2d-x是一套基于C++的引擎,C++的内存机制,如果采用new关键字声明一个对象而没有手动delete掉,那么申请的内存就不会被回收,进而造成内存泄露。cocos2d-x是采用引用计数的方式管理内存,基本的原则就是当构造一个对象时,引用计数为1,每次进行retain操作的时候,引用计数加1,每次进行release操作的时候,引用计数减1,当一个对象的引用计数为0时,就将这个对象delete掉,详细的机制可以看CCObject的源码。
那么当你在cocos2d-x中new一个对象,引擎又是怎么帮你管理的呢?Cocos2d-x自身的自动内存管理机制其实是不推荐使用的,因为在游戏开发的过程中会出现很多的引用计数问题,它的大概的管理流程是这样的,当你create一个对象时,就会自动对这个对象进行一次autorelease的操作,这个autorelease做了很多的事,简单地说就是,执行了autorelease的对象就表明这个对象处于自动管理的状态,会在内存管理池CCPoolManager中添加这个对象,并且在自动释放内存池CCAutoreleasePool的堆栈中申请一块内存池放入这个对象。之后在不需要用的时候引擎会自动delete掉它,引擎是用单一的线程来进行场景的绘制,通过不断调用mainLoop这个函数,这个函数除了进行场景的绘制,也会调用CCPoolManger函数的pop方法对自动管理的对象进行释放操作,pop方法会对CCAutoreleasePool堆栈栈顶的内存池进行操作,将池内的对象标记为非自动管理状态,并进行一次release操作,清除引用计数为1的对象,然后取出前一个入栈的内存池等待下一轮的释放。那么这种机制有时就会出现错误,如果我们没有进行retain操作,new出来的对象可能在下一帧就已经release掉了,出现空指针的错误,而如果我们自己进行手动的retain操作的话,一次的clear操作只能将引用计数减1,无法delete掉对象,又会造成内存泄露,所以很不推荐使用自带的自动内存管理。cocos2d-x中存在大量的静态工厂方法,这些方法全都对this指针调用了autorelease方法,通过静态工厂来生成对象可以简化代码。有兴趣的同学可以自己写一个内存管理的控件哦!难度不大就看效率咯!
二、图片的缓存和加载方式
在IOS上,所有的图片都会自动缩放到2的N次方,这就意味着一张1024*1025大小的图片和1024*2048的图片占用的内存是一样的,而图片占用的内存可以由公式:长*宽*4来计算得到,所以一张1024*1024的图片占用的内存大小是1024*1024*4,也就是4M,IOS上最大支持的图片尺寸是2048*2048。
在cocos2d-x中进行图片加载时,如果是第一次加载图片就要把图片加载到缓存,然后从缓存中加载图片,如果缓存中已经有了,就直接从缓存中加载。
图片的缓存有两种类型,一种是CCTextureCache,另一种是CCSpriteFrameCache。
第一种是普通的图片加载缓存,一般直接加载的图片都会放到这个缓存里面,这是一个会经常要用到的缓存,所以又必要熟悉它的一些函数,要知道这个缓存如果不手动释放,它是会一直占用内存的,它有很多函数接口,都是为了方便进行内存管理的,例如removeAllTextures()(清空所有的缓存),removeUnusedTextures()(清除没用的缓存),dumpCachedTextureInfo()(输出缓存的信息)等等。我自己针对这个Cache的管理写了一个类,不过感觉在很大数据量的时候效率可能会比较低,还有待改进。
那么这两种加载方式对内存有什么具体的影响呢?
首先为了方便我们得到内存的具体信息,我们要使用检测内存泄露的工具,我用的是VLD,具体的使用方法我在另一篇博文中有分享。
以加载一张1024*1024的图片为例。
如果使用CCSprite *pSprite = CCSprite::SpriteWithFile("background.png");可以看到内存增加了4M左右的大小,如果对这张图片进行渲染的话,例如使用addchild(pSprite);,内存又会增加4M,所以一张图片的加载实际上占用了2倍的内存。
如果使用CCSpriteFrameCache::sharedSpriteFrameCache->addSpriteFramesWithFile("p.plist");,假设大图的大小为2048*2048,那么内存直接增加了16M,此时想要加载大图中的一张小图,内存又会增加16M,不过以后再使用大图中的图片就不会增加内存了。
三、常见的内存管理的方法:
1.在适当的时候释放内存:有效地释放无用的内存是常见的缓解内存压力的手段,比如在场景切换的时候将前一个场景的内存全部释放掉,不过需要注意的是常见切换的时机,一般情况下要确保你切换到下一场景之前要将该场景初始化,有时强制释放某些资源会导致正在执行的动画失去引用而出现异常,这时可以通过调用CCActionManager::sharedManager->removeAllActions();释放动画。
2.使用纹理贴图集的方法,尽量拼接图片,使得图片的边长保持在2的N次方,同时最好将有一点逻辑关系的图片打包在一张大图里,从而能够有效的节约内存。
3.使用CCSpriteBatchNode来减少相同图片的渲染操作。