編輯:關於Android編程
Cocos2d-x引擎的核心是用C++編寫的,那對於所有使用該引擎的游戲開發人員來說,內存管理是一道繞不過去的坎。
關於Cocos2d-x內存管理,網上已經有了許多參考資料,有些資料寫的頗為詳實,因為在內存管理這塊我不想多費筆墨,只是更多的將思路描述清楚。
一、對象內存引用計數
Cocos2d-x內存管理的基本原理就是對象內存引用計數,Cocos2d-x將內存引用計數的實現放在了頂層父類CCObject中,這裡將涉及引用計數的CCObject的成員和方法摘錄出來:
復制代碼 代碼如下:
class CC_DLL CCObject : public CCCopying
{
public:
… …
protected:
// count of references
unsigned int m_uReference;
// count of autorelease
unsigned int m_uAutoReleaseCount;
public:
void release(void);
void retain(void);
CCObject* autorelease(void);
… ….
}
CCObject::CCObject(void)
: m_nLuaID(0)
, m_uReference(1) // when the object is created, the reference count of it is 1
, m_uAutoReleaseCount(0)
{
… …
}
void CCObject::release(void)
{
CCAssert(m_uReference > 0, "reference count should greater than 0");
–m_uReference;
if (m_uReference == 0)
{
delete this;
}
}
void CCObject::retain(void)
{
CCAssert(m_uReference > 0, "reference count should greater than 0");
++m_uReference;
}
CCObject* CCObject::autorelease(void)
{
CCPoolManager::sharedPoolManager()->addObject(this);
return this;
}
先不考慮autorelease與m_uAutoReleaseCount(後續細說)。計數的核心字段是m_uReference,可以看到:
* 當一個Object初始化(被new出來時),m_uReference = 1;
* 當調用該Object的retain方法時,m_uReference++;
* 當調用該Object的release方法時,m_uReference–,若m_uReference減後為0,則delete該Object。
二、手工對象內存管理
在上述對象內存引用計數的原理下,我們得出以下Cocos2d-x下手工對象內存管理的基本模式:
復制代碼 代碼如下:
CCObject *obj = new CCObject();
obj->init();
…. …
obj->release();
在Cocos2d-x中CCDirector就是一個手工內存管理的典型:
CCDirector* CCDirector::sharedDirector(void)
{
if (!s_SharedDirector)
{
s_SharedDirector = new CCDisplayLinkDirector();
s_SharedDirector->init();
}
return s_SharedDirector;
}
void CCDirector::purgeDirector()
{
… …
// delete CCDirector
release();
}
三、自動對象內存管理
所謂的“自動對象內存管理”,指的就是哪些不再需要的object將由Cocos2d-x引擎替你釋放掉,而無需你手工再調用Release方法。
自動對象內存管理顯然也要遵循內存引用計數規則,只有當object的計數變為0時,才會釋放掉對象的內存。
自動對象內存管理的典型模式如下:
復制代碼 代碼如下:
CCYourClass *CCYourClass::create()
{
CCYourClass*pRet = new CCYourClass();
if (pRet && pRet->init())
{
pRet->autorelease();
return pRet;
}
else
{
CC_SAFE_DELETE(pRet);
return NULL;
}
}
一般我們通過一個單例模式創建對象,與手工模式不同的地方在於init後多了一個autorelease調用。這裡再把autorelease調用的實現摘錄一遍:
復制代碼 代碼如下:
CCObject* CCObject::autorelease(void)
{
CCPoolManager::sharedPoolManager()->addObject(this);
return this;
}
追溯addObject方法:
復制代碼 代碼如下:
// cocoa/CCAutoreleasePool.cpp
void CCPoolManager::addObject(CCObject* pObject)
{
getCurReleasePool()->addObject(pObject);
}
void CCAutoreleasePool::addObject(CCObject* pObject)
{
m_pManagedObjectArray->addObject(pObject);
CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");
++(pObject->m_uAutoReleaseCount);
pObject->release(); // no ref count, in this case autorelease pool added.
}
// cocoa/CCArray.cpp
void CCArray::addObject(CCObject* object)
{
ccArrayAppendObjectWithResize(data, object);
}
// support/data_support/ccCArray.cpp
void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)
{
ccArrayEnsureExtraCapacity(arr, 1);
ccArrayAppendObject(arr, object);
}
void ccArrayAppendObject(ccArray *arr, CCObject* object)
{
CCAssert(object != NULL, "Invalid parameter!");
object->retain();
arr->arr[arr->num] = object;
arr->num++;
}
調用層次挺深,涉及的類也眾多,這裡歸納總結一下。
Cocos2d-x的自動對象內存管理基於對象引用計數以及CCAutoreleasePool(自動釋放池)。引用計數前面已經說過了,這裡單說自動釋放池。Cocos2d-x關於自動對象內存管理的基本類層次結構如下:
復制代碼 代碼如下:
CCPoolManager類 (自動釋放池管理器)
– CCArray* m_pReleasePoolStack; (自動釋放池棧,存放CCAutoreleasePool類實例)
CCAutoreleasePool類
– CCArray* m_pManagedObjectArray; (受管對象數組)
CCObject關於內存計數以及自動管理有兩個字段:m_uReference和m_uAutoReleaseCount。前面在手工管理模式下,我只提及了m_uReference,是m_uAutoReleaseCount該亮相的時候了。我們沿著自動釋放對象的創建步驟來看看不同階段,這兩個重要字段的值都是啥,代表的是啥含義:
復制代碼 代碼如下:
CCYourClass*pRet = new CCYourClass(); m_uReference = 1; m_uAutoReleaseCount = 0;
pRet->init(); m_uReference = 1; m_uAutoReleaseCount = 0;
pRet->autorelease();
m_pManagedObjectArray->addObject(pObject); m_uReference = 2; m_uAutoReleaseCount = 0;
++(pObject->m_uAutoReleaseCount); m_uReference = 2; m_uAutoReleaseCount = 1;
pObject->release(); m_uReference = 1; m_uAutoReleaseCount = 1;
在調用autorelease之前,兩個值與手工模式並無差別,在autorelease後,m_uReference值沒有變,但m_uAutoReleaseCount被加1。
m_uAutoReleaseCount這個字段的名字很容易讓人誤解,以為是個計數器,但實際上絕大多數時刻它是一個標識的角色,以前版本代碼中有一個布爾字段m_bManaged,似乎後來被m_uAutoReleaseCount替換掉了,因此m_uAutoReleaseCount兼有m_bManaged的含義, 也就是說該object是否在自動釋放池的控制之下,如果在自動釋放池的控制下,自動釋放池會定期調用該object的release方法,直到該 object內存計數降為0,被真正釋放。否則該object不能被自動釋放池自動釋放內寸,需手工release。這個理解非常重要,再後面我們能用到這個理解。
四、自動釋放時機
通過autorelease我們已經將object放入autoreleasePool中,那究竟何時對象會被釋放呢?答案是每幀執行一次自動內存對象釋放操作。
在“Hello,Cocos2d-x”一文中,我們講過整個Cocos2d-x引擎的驅動機制在於GLThread的guardedRun函數,後者會 “死循環”式(實際幀繪制頻率受到屏幕vertsym信號的影響)的調用Render的onDrawFrame方法實現,而最終程序會進入 CCDirector::mainLoop方法中,也就是說mainLoop的執行頻率是每幀一次。我們再來看看mainLoop的實現:
復制代碼 代碼如下:
void CCDisplayLinkDirector::mainLoop(void)
{
if (m_bPurgeDirecotorInNextLoop)
{
m_bPurgeDirecotorInNextLoop = false;
purgeDirector();
}
else if (! m_bInvalid)
{
drawScene();
// release the objects
CCPoolManager::sharedPoolManager()->pop();
}
}
這次我們要關注的不是drawScene,而是 CCPoolManager::sharedPoolManager()->pop(),顯然在游戲未退出 (m_bPurgeDirecotorInNextLoop決定)的條件下,CCPoolManager的pop方法每幀執行一次,這就是自動釋放池執行的起點。
復制代碼 代碼如下:
void CCPoolManager::pop()
{
if (! m_pCurReleasePool)
{
return;
}
int nCount = m_pReleasePoolStack->count();
m_pCurReleasePool->clear();
if(nCount > 1)
{
m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount – 2);
}
}
真正釋放對象的方法是m_pCurReleasePool->clear()。
復制代碼 代碼如下:
void CCAutoreleasePool::clear()
{
if(m_pManagedObjectArray->count() > 0)
{
CCObject* pObj = NULL;
CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)
{
if(!pObj)
break;
–(pObj->m_uAutoReleaseCount);
}
m_pManagedObjectArray->removeAllObjects();
}
}
void CCArray::removeAllObjects()
{
ccArrayRemoveAllObjects(data);
}
void ccArrayRemoveAllObjects(ccArray *arr)
{
while( arr->num > 0 )
{
(arr->arr[--arr->num])->release();
}
}
不出預料,當前自動釋放池遍歷每個“受控制”Object,–m_uAutoReleaseCount,並調用該object的release方法。
我們接著按釋放流程來看看m_uAutoReleaseCount和m_uReference值的變化:
復制代碼 代碼如下:
CCPoolManager::sharedPoolManager()->pop(); m_uReference = 0; m_uAutoReleaseCount = 0;
五、自動釋放池的初始化
自動釋放池本身是何時出現的呢?回顧一下Cocos2d-x引擎的初始化過程(android版),引擎初始化實在Render的onSurfaceCreated方法中進行的,我們不難追蹤到以下代碼:
復制代碼 代碼如下:
//hellocpp/jni/hellocpp/main.cpp
Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit {
//這裡CCDirector第一次被創建
if (!CCDirector::sharedDirector()->getOpenGLView())
{
CCEGLView *view = CCEGLView::sharedOpenGLView();
view->setFrameSize(w, h);
AppDelegate *pAppDelegate = new AppDelegate();
CCApplication::sharedApplication()->run();
}
}
CCDirector* CCDirector::sharedDirector(void)
{
if (!s_SharedDirector)
{
s_SharedDirector = new CCDisplayLinkDirector();
s_SharedDirector->init();
}
return s_SharedDirector;
}
bool CCDirector::init(void)
{
setDefaultValues();
… …
// create autorelease pool
CCPoolManager::sharedPoolManager()->push();
return true;
}
六、探尋Cocos2d-x內核對象的自動化內存釋放
前面我們基本了解了Cocos2D-x的自動化內存釋放原理。如果你之前翻看過一些Cocos2d-x的內核源碼,你會發現很多內核對象都是通過單例模式create出來的,也就是說都使用了autorelease將自己放入自動化內存釋放池中被管理。
比如我們在HelloCpp中看到過這樣的代碼:
復制代碼 代碼如下:
//HelloWorldScene.cpp
bool HelloWorld::init() {
…. ….
// add "HelloWorld" splash screen"
CCSprite* pSprite = CCSprite::create("HelloWorld.png");
// position the sprite on the center of the screen
pSprite->setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
// add the sprite as a child to this layer
this->addChild(pSprite, 0);
… …
}
CCSprite采用自動化內存管理模式create object(cocos2dx/sprite_nodes/CCSprite.cpp),之後將自己加入到HelloWorld這個CCLayer實例 中。按照上面的分析,create結束後,CCSprite object的m_uReference = 1; m_uAutoReleaseCount = 1。一旦如此,那麼在下一幀時,該object就會被CCPoolManager釋放掉。但我們在屏幕上依舊可以看到該Sprite的存在,這是怎麼回事呢?
問題的關鍵就在this->addChild(pSprite, 0)這行代碼中。addChild方法實現在CCLayer的父類CCNode中:
復制代碼 代碼如下:
// cocos2dx/base_nodes/CCNode.cpp
void CCNode::addChild(CCNode *child, int zOrder, int tag)
{
… …
if( ! m_pChildren )
{
this->childrenAlloc();
}
this->insertChild(child, zOrder);
… …
}
void CCNode::insertChild(CCNode* child, int z)
{
m_bReorderChildDirty = true;
ccArrayAppendObjectWithResize(m_pChildren->data, child);
child->_setZOrder(z);
}
void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)
{
ccArrayEnsureExtraCapacity(arr, 1);
ccArrayAppendObject(arr, object);
}
void ccArrayAppendObject(ccArray *arr, CCObject* object)
{
CCAssert(object != NULL, "Invalid parameter!");
object->retain();
arr->arr[arr->num] = object;
arr->num++;
}
又是一系列方法調用,最終我們來到了ccArrayAppendObject方法中,看到了陌生而又眼熟的retain方法調用。
在本文開始我們介紹CCObject時,我們知道retain是CCObject的一個方法,用於增加m_uReference計數。而實際上retain還隱含著“保留”這層意思。
在完成this->addChild(pSprite, 0)調用後,CSprite object的m_uReference = 2; m_uAutoReleaseCount = 1,這很關鍵。
我們在腦子裡再過一下自動釋放池釋放object的過程:–m_uReference, –m_uAutoReleaseCount。一幀之後,兩個值變成了m_uReference = 1; m_uAutoReleaseCount = 0。還記得前面說過的m_uAutoReleaseCount的另外一個非計數含義麼,那就是表示該object是否“受控”,現在值為0,顯然不再受自動釋放池的控制了,後續即便再執行100次內存自動釋放,也不會影響到該object的存活。
後續要想釋放這個“精靈”,我們還是需要手工調用release,或再調用其autorelease方法。
關於Andorid的第三方庫導入和其他知識:現在講的都是些基礎的東西,東西會一步步往上升的,知道操作的可以在這裡找找問題 ,順便溫習下。然後不知道的就在這裡學習下。第三方
Android Toolbar:ToolBar是Android 5.0(API Level 21)之後用來取代ActionBar的ToolBar的優勢:Toolbar本身
Animation 概述安裝中的動畫分為三種:補間動畫(Tween Animation),幀動畫(Frame Animation)和屬性動畫(Property Anima
前言給自己的APP增加相機是一個不錯的功能,在我們打開相機時,如果能動態給我們的臉上貼上標簽,或者是貼上一個卡通的眼睛,最後點擊拍照,一張合成的圖片就產生了,這是不是一個