cocos2d-x回调的代码要规避autorelease对象意外释放的问题,这是昨日调试Android一个平台登录闪退的bug时发现。更准确地说,cocos2d-x的autoreleasepool机制只只适用于单线程。
如下是部分cocos2d-x 2.14源代码
CCObject* CCObject::autorelease(void)
{
CCPoolManager::sharedPoolManager()->addObject(this);
return this;
}
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.
}
void CCPoolManager::addObject(CCObject* pObject)
{
getCurReleasePool()->addObject(pObject);
}
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);
}
}
void CCDisplayLinkDirector::mainLoop(void)
{
if (m_bPurgeDirecotorInNextLoop)
{
m_bPurgeDirecotorInNextLoop = false;
purgeDirector();
}
else if (! m_bInvalid)
{
drawScene();
// release the objects
CCPoolManager::sharedPoolManager()->pop();
}
}
mainLoop函数几乎就是cocos2dx一帧循环的主体,显然auorelease实际是将CCObject对象委托给CCPoolManager单例对象,在一帧循环mainLoop收尾处对这些auorelease过的对象调用实际的release函数。咋眼一看所有的reference=1的autorelease过的对象保证在这一帧有效,在一帧最后被release。但是显然多线程就不能保证了,假如在另一个线程hreadB中使用XXX::create产生的对象A,就有hreadB线程内对象A正在使用,但主线程就在这时进入mainLoop的CCPoolManager::sharedPoolManager()->pop()将对象A释放的危险。
当然就这一点一般都能想的到,但Android的Java层通过JNI机制回调native函数时实际上往往回调于一个Handle的消息循环,Handle的消息循环实际上就是另一个线程啊!!!细思恐极啊!!!!这样以来所有JNI机制的native回调全都不安全啊!!!!你甚至不能使用所有cocos2dx里面的对象和函数,因为autorelease充斥于整个cocos2dx的代码,如果你要使用也要在胆颤心惊中查看绝大多数函数和对象的源码。。。。
解决方法
一切都是多线程同步惹得祸,也可以用同步解决。来个萎缩的方法,读源代码可知mainLoop中CCPoolManager::sharedPoolManager()->pop()在这一步释放这一帧所有产生的autorelease对象。那干脆加个线程锁,保证回调时不会释放autorelease对象。
修改源代码如下
struct CCMutex {
CCMutex(){
pthread_mutex_init(&mutex_, NULL);
}
~CCMutex(){
pthread_mutex_destroy(&mutex_);
}
void Lock(){
pthread_mutex_lock(&mutex_);
}
void unLock(){
pthread_mutex_unlock(&mutex_);
}
private:
pthread_mutex_t mutex_;
};
class CC_DLL CCPoolManager
{
CCArray* m_pReleasePoolStack;
CCAutoreleasePool* m_pCurReleasePool;
CCAutoreleasePool* getCurReleasePool();
public:
CCPoolManager();
~CCPoolManager();
void finalize();
void push();
void pop();
void removeObject(CCObject* pObject);
void addObject(CCObject* pObject);
static CCPoolManager* sharedPoolManager();
static void purgePoolManager();
friend class CCAutoreleasePool;
CCMutex m_mutex;
};
void CCPoolManager::pop()
{
m_mutex.Lock();
if (! m_pCurReleasePool)
{
m_mutex.unLock();
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 = NULL;*/
m_mutex.unLock();
}
jni机制native回调函数同样加入
void Java_com_xxxx_callback(...) { CCPoolManager::sharedPoolManager()->m_mutex.Lock(); //xxxx其他 CCPoolManager::sharedPoolManager()->m_mutex.unLock(); }