1. 程式人生 > >【玩轉cocos2d-x之二十三】多執行緒和同步03-圖片非同步載入

【玩轉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.耗時的是什麼?

首先要理解的是耗時的動作是什麼,只有把耗時的工作真正抓出來丟到工作執行緒上,非同步載入才有意義。我們知道,圖片在記憶體中是以紋理的形式存在的,而圖片的載入,通俗

來講也就是紋理的生成,這就是耗時的原因。那CCTexureCache中addImage(同步載入)和addImageAysnc(非同步載入)分別做了什麼事?

(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);
}

4.效果

使用非同步載入plist方式處理:

5.原始碼下載