Cocos2d-x 缓存机制:预加载与重复使用

缓存在软硬件设计中是一个十分常见的优化方法,多用于高性能软硬件的设计。简单地说,缓存就是利用存储器的速度等级差异,将低速存储中使用频率高的内容加载到高速存储中,这样可以有效提高访问速度。比如将常用的图片资源从磁盘读到内存,将常用的程序段从内存搬到CPU的高速缓存中。

移动设备比较常用的是外存到内存的缓存。尽管大部分手机使用闪存作为外部存储已经比PC上磁盘的速度快得多,但相比内存而言还是差了两到三个数量级。从闪存内读取一张图片平均会耗费接近0.1秒的时间,这样反复加载将占用非常多的系统资源。而缓存机制可以预先加载我们需要的内容到内存,并且在之后的操作中重复使用。

缓存空间毕竟有限,每一个缓存都应该实现合理的换入换出机制来保证缓存中的内容确实是最需要被反复利用的。同时,缓存应该尽量透明化,也就是说,在不主动调用的情况下缓存就应该生效。幸运的是,我们不需要自己实现缓存,因为Cocos2d-x已经为我们提供了足够强大的实现。引擎中存在3个缓存类,都是全局单例模式。

1 CCTextureCache

首先是最底层也最有效的纹理缓存CCTextureCache,这里缓存的是加载到内存中的纹理资源,也就是图片资源。其原理是对加入缓存的纹理资源进行一次引用,使其引用计数加一,保持不被清除,而Cocos2d-x的渲染机制是可以重复使用同一份纹理在不同的场合进行绘制,从而达到重复使用,降低内存和GPU运算资源的开销的目的。常用的是如下所示的3个

1 static CCTextureCache* sharedTextureCache(); //返回纹理缓存的全局单例
2 CCTexture2D* addImage(const char* fileimage); //添加一张纹理图片到缓存中
3 void removeUnusedTextures(); //清除不使用的纹理

在这3个接口中,CCTextureCache屏蔽了加载纹理的许多细节;addImage函数会返回一个纹理CCTexture2D的引用,可能是新加载到内存的,也可能是之前已经存在的;而removeUnusedTextures则会释放当前所有引用计数为1的纹理,即目前没有被使用的纹理。后面会看到,引用计数的内存管理方式为缓存的设计带来了很大的便利。

在这3个接口中,CCTextureCache屏蔽了加载纹理的许多细节;addImage函数会返回一个纹理CCTexture2D的引用,可能是新加载到内存的,也可能是之前已经存在的;而removeUnusedTextures则会释放当前所有引用计数为1的纹理,即目前没有被使用的纹理。后面会看到,引用计数的内存管理方式为缓存的设计带来了很大的便利。

2 CCSpriteFrameCache

第二个则是精灵框帧缓存。顾名思义,这里缓存的是精灵框帧CCSpriteFrame,它主要服务于多张碎图合并出来的纹理图片。这种纹理在一张大图中包含了多张小图,直接通过CCTextureCache引用会有诸多不便,因而衍生出来精灵框帧的处理方式,即把截取好的纹理信息保存在一个精灵框帧内,精灵通过切换不同的框帧来显示出不同的图案。

CCSpriteFrameCache的常用接口和CCTextureCache类似,不再赘述了,唯一需要注意的是添加精灵帧的配套文件-- 一个plist文件和一张大的纹理图。下面列举了CCSpriteFrame Cache常用的方法:

1 static CCSpriteFrameCache* sharedSpriteFrameCache(void); //全局共享的缓存单例
2 void addSpriteFramesWithFile(const char *pszPlist); //通过plist配置文件添加一组精灵帧
3 void removeUnusedSpriteFrames(void); //清理无用缓存

3 CCAnimationCache

最后一个是CCAnimationCache动画的缓存。通常情况下,对于一个精灵动画,每次创建时都需要加载精灵帧,按顺序添加到数组,再创建对应动作类,这是一个非常烦琐的计算过程。对于使用频率高的动画,比如鱼的游动,将其加入缓存可以有效降低每次创建的巨大消耗。由于这个类的目的和缓存内容都非常简单直接,所以其接口也是最简单明了的,如下所示:

1 static CCAnimationCache* sharedAnimationCache(void);//全局共享的缓存单例
2 void addAnimation(CCAnimation *animation, const char * name);//添加一个动画到缓存
3 void removeAnimationByName(const char* name);//移除一个指定的动画
4 CCAnimation* animationByName(const char* name);//获得事先存入的动画

唯一不一样的是,这次动画缓存需要我们手动维护全部动画信息。也因为加载帧动画完全是代码操作的,目前还没有配置文件指导,所以不能像另外两个缓存那样透明化。实际上,如果考虑到两个场景间使用的动画基本不会重复,可以直接清理整个动画缓存。

所以,在场景切换时我们应该加入如下的清理缓存操作:

1 void releaseCaches()
2 {
3     CCAnimationCache::purgeSharedAnimationCache();
4      CCSpriteFrameCache::sharedSpriteFrameCache()->removeUnusedSpriteFrames();
5     CCTextureCache::sharedTextureCache()->removeUnusedTextures();
6 }

值得注意的是清理的顺序,应该先清理动画缓存,然后清理精灵帧,最后是纹理。按照引用层级由高到低,以保证释放引用有效。

相关推荐