【玩轉cocos2d-x之二十三】多執行緒和同步03-圖片非同步載入
cocos2d-x中和Android,Windows都一樣,如果在主執行緒中處理一些耗時操作,那麼主執行緒就會出現阻塞現象,表現在介面上就是卡住,未響應等情況。為了避免這種情況的出現,我們需要在後臺開闢工作執行緒進行資料的處理,再採用訊息傳遞或者其他形式來通知主執行緒進行UI變化。最常見的情況就是遊戲進入前的loading。
1.圖片的非同步載入
在多執行緒和同步的第一篇介紹到使用pthread庫的時候,講到由於CCAutoreleasePool不是執行緒安全的,所以不能在工作執行緒中引入cocos2d-x相關的API(其實並不是所有的API都不能使用)。但是cocos2d-x顯然考慮到這個問題了,所以它本身就幫我們封裝好了一個API,避免了還要手動引入pthread庫的尷尬。
void CCTextureCache::addImageAsync(const char *path, CCObject *target, SEL_CallFuncO selector)
其中path是圖片的位置,selector是載入完成時的回撥函式。很方便,如果需要載入很多圖片的話,對每一個進行回撥處理,然後在update中更新UI即可。2.plist的非同步載入
可是由於記憶體原因,大部分情況下圖片會被合成打包,同時帶入plist。這時候如何進行圖片的非同步載入呢?這個時候就需要對addImageAsync的原始碼進一步的探究了。
2.1.耗時的是什麼?
首先要理解的是耗時的動作是什麼,只有把耗時的工作真正抓出來丟到工作執行緒上,非同步載入才有意義。我們知道,圖片在記憶體中是以紋理的形式存在的,而圖片的載入,通俗
(1)addImage
可以看出addImage使用同步的方式生成了紋理,也就是在主執行緒中進行了耗時的載入操作。
//...cocos2d-x維護著一個全域性紋理,在判斷紋理是否已存在 if (! texture) { do { //...判斷圖片格式 pImage = new CCImage(); CC_BREAK_IF(NULL == pImage); bool bRet = pImage->initWithImageFile(fullpath.c_str(), eImageFormat); CC_BREAK_IF(!bRet); texture = new CCTexture2D(); //開闢紋理空間 if( texture && texture->initWithImage(pImage) ) //使用CCImage初始化紋理 { #if CC_ENABLE_CACHE_TEXTURE_DATA // cache the texture file name VolatileTexture::addImageTexture(texture, fullpath.c_str(), eImageFormat); #endif m_pTextures->setObject(texture, pathKey.c_str()); texture->release(); } else { CCLOG("cocos2d: Couldn't create texture for file:%s in CCTextureCache", path); } } while (0); } //...釋放資源,返回紋理
(2)addImageAsync
addImageAsync則是在工作執行緒中載入圖片,然後通過排程器進行紋理的轉換。//建立工作執行緒用於後臺載入圖片
pthread_create(&s_loadingThread, NULL, loadImage, NULL);
//建立排程佇列,用來根據已載入的圖片進行紋理轉換
CCDirector::sharedDirector()->getScheduler()->scheduleSelector(schedule_selector(CCTextureCache::addImageAsyncCallBack), this, 0, false);
void CCTextureCache::addImageAsyncCallBack(float dt)
{
//...
CCTexture2D *texture = new CCTexture2D(); //開闢紋理空間
#if 0 //TODO: (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
texture->initWithImage(pImage, kCCResolutioniPhone);
#else
texture->initWithImage(pImage); //使用CCImage初始化紋理
#endif
#if CC_ENABLE_CACHE_TEXTURE_DATA
VolatileTexture::addImageTexture(texture, filename, pImageInfo->imageType);
#endif
//...將加入autorelease,進行載入後的回撥函式的呼叫,釋放相關資源
}
2.2.plist載入的原理
之前使用plist是這樣子的:
void CCSpriteFrameCache::addSpriteFramesWithFile(const char *pszPlist)//傳入plist檔案即可
在它的實現中,發現了這麼一句:
CCTexture2D *pTexture = CCTextureCache::sharedTextureCache()->addImage(texturePath.c_str());
原來addSpriteFramesWithFile會先查詢是否存在紋理,否則會在.plist的目錄下尋找同名圖片,然後呼叫同步的addImage函式來生成紋理。這也就是為什麼只加載了plist,而紋理就會存在的原因了。
2.3.非同步載入plist
知道了這些,那就讓addSpriteFramesWithFile呼叫非同步的addImageAsync函式就可以了,當然不需要改原始碼,因為CCSpriteFrameCache還提供瞭如下的plist載入方式:
void CCSpriteFrameCache::addSpriteFramesWithFile(const char *pszPlist, CCTexture2D *pobTexture)
所以我們可以手動非同步生成紋理後,在回撥函式中和.plist一起傳入addSpriteFramesWithFile,搞定!還記得剛開始的selector麼?生成的紋理會作為引數傳入這個回撥函式中,完美!
2.4.注意
只要注意一點的是,在使用任何plist中的小圖片時,引擎並不會為每一張小圖片生成一個紋理,而是共用了大圖片的紋理,同時指定了小圖片的座標和長寬。
3.示例
其中ui_text.png是大圖片,raffle_b_friend.png和raffle_b_diamond.png是兩張小圖片。
bool CTestLayer::init()
{
bool bRet=false;
do
{
CC_BREAK_IF(!CCLayer::init());
//addImage or addImageAsync中建立紋理
CCTextureCache::sharedTextureCache()->addImageAsync("ui_text.png",this,callfuncO_selector(CTestLayer::showSprite));
bRet=true;
} while (0);
return bRet;
}
void CTestLayer::showSprite(CCObject* obj)
{
CCTexture2D* texture_ui_text=(CCTexture2D*)obj;//傳入的obj即是非同步生成的紋理
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("ui_text.plist",texture_ui_text);//通過紋理和.plist檔案加入CCSpriteFrameCache
CCSprite* raffle_b_friend=CCSprite::createWithSpriteFrameName("raffle_b_friend.png");//盡情使用小圖片吧
raffle_b_friend->setPosition(ccp(100,100));
this->addChild(raffle_b_friend);
//錯誤,只能獲取ui_text.png的紋理
//CCTexture2D* raffle_b_diamond_texture=CCTextureCache::sharedTextureCache()->textureForKey("raffle_b_diamond.png");
//正確,可以用這種先獲取精靈幀,再從精靈幀中獲取ui_text的紋理,以及大小,來構建CCSprite
CCSpriteFrame* raffle_b_diamond_spriteframe=CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName("raffle_b_diamond.png");
CCTexture2D* texture=raffle_b_diamond_spriteframe->getTexture();
CCRect rect=raffle_b_diamond_spriteframe->getRect();
CCSprite* raffle_b_diamond=CCSprite::createWithTexture(texture,rect); //如果紋理需要旋轉,setRotation吧
raffle_b_diamond->setRotation(false);
raffle_b_diamond->setPosition(ccp(300,100));
this->addChild(raffle_b_diamond);
}