Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Cocos2d-x的內存管理總結

Cocos2d-x的內存管理總結

編輯:關於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方法。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved