緩存在軟硬件設計中是一個十分常見的優化方法,多用於高性能軟硬件的設計。簡單地說,緩存就是利用存儲器的速度等級差異,將低速存儲中使用頻率高的內容加載到高速存儲中,這樣可以有效提高訪問速度。比如將常用的圖片資源從磁盤讀到內存,將常用的程序段從內存搬到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 }
值得注意的是清理的順序,應該先清理動畫緩存,然后清理精靈幀,最后是紋理。按照引用層級由高到低,以保證釋放引用有效。