ShareSDK造成App崩潰的一個BUG原因分析以及Fix方法
近期研究了一下Game App做社交分享,最后選擇了ShareSDK來集成,不僅是因為ShareSDK支持國內(nèi)外主流社交平臺,更重要的是ShareSDK提供了專門的 cocos2d-x集成方案,有專門的文檔和代碼Demo供開發(fā)者參考。
文檔中提到了三種集成方式:純Java方式、plugin-x方式以及Cocos2d-x專用組件方式,這里選擇了ShareSDK Cocos2d-x專用組件(v2.3.7版本)的方式。按照文檔中描述的步驟進行的相對順利,在各個社交平臺的appkey生效后,我們對demo app進行了測試,居然發(fā)現(xiàn)app經(jīng)常隨機性的崩潰,有時甚至是每次都崩潰,經(jīng)過深入分析,發(fā)現(xiàn)這是ShareSDK Cocos2d-x專用組件的一個嚴重Bug,下面詳細說明一下Bug的產(chǎn)生原因以及Fix方法。
一、App崩潰的場景和代碼位置
發(fā)生崩潰的場景如下:
App Demo中有一個"Share"按鈕,點擊該按鈕,App Demo向已經(jīng)授權(quán)的社交平臺分享一些Test Content,而App Demo就在收到分享結(jié)果應(yīng)答時發(fā)生了崩潰。
代碼位置大致如下:
void AppDemo::onShareClick(CCObject* sender)
{
… …
C2DXShareSDK::showShareMenu(NULL, content,
CCPointMake(100, 100),
C2DXMenuArrowDirectionLeft,
shareResultHandler);
}
void shareResultHandler(C2DXResponseState state, C2DXPlatType platType,
CCDictionary *shareInfo, CCDictionary *error)
{
switch (state) {
case C2DXResponseStateSuccess:
CCLog("Share Ok");
break;
case C2DXResponseStateFail:
CCLog("Share Failed");
break;
default:
break;
}
}
崩潰的位置大致就在回調(diào)shareResultHandler前后的某個位 置,比較隨機。
二、現(xiàn)象分析
通過查看Eclipse logcat窗口的調(diào)試日志,我們發(fā)現(xiàn)一些規(guī)律,一些在“Share Ok后的崩潰打印出如下日志:
04-16 01:28:33.890: D/cocos2d-x debug info(1748): Share Ok
04-16 01:28:34.090: D/cocos2d-x debug info(1748): Assert failed: reference count should greater than 0
04-16 01:28:34.090: E/cocos2d-x assert(1748): /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/Cpp/temp/AppDemo/proj.android/../../../../../cocos2dx/cocoa/CCObject.cpp function:release line:81
04-16 01:28:34.130: A/libc(1748): Fatal signal 11 (SIGSEGV) at 0×00000003 (code=1), thread 1829 (Thread-122)
猜測一下,似乎是某個CCObject在真正Release前已經(jīng)被釋放了,然后后續(xù)被引用時觸發(fā)內(nèi)存非法訪問。Cocos2d-x采用的是內(nèi)存 計數(shù)的內(nèi)存管理機制,在我的《Cocos2d-x內(nèi)存管理-繞不過去的坎》一文中有描述。了解Cocos2d-x的內(nèi)存管理機制是理解這個Bug 的前提條件。
三、原因分析
看來不得不挖掘一下ShareSDK組件的代碼了。AppDemo中ShareSDK組件的代碼分為兩個部分:AppDemo/Classes /C2DXShareSDK和AppDemo/proj.android/src/cn/sharesdk。前者是C++代碼,后面則是Java 代碼,兩者通過jni調(diào)用聯(lián)系在一起。我們重點來找出分享應(yīng)答返回來時的關(guān)鍵聯(lián)系。
集成ShareSDK的Cocos2d-x程序會在主Activity的onCreate方法中調(diào)用ShareSDKUtils.prepare();
我們來看看prepare方法的實現(xiàn):
//AppDemo/proj.android/src/cn/sharesdk/ShareSDKUtils.java
public class ShareSDKUtils {
private static boolean DEBUG = true;
private static Context context;
private static PlatformActionListener paListaner;
private static Hashon hashon;
… …
public static void prepare() {
UIHandler.prepare();
context = Cocos2dxActivity.getContext().getApplicationContext();
hashon = new Hashon();
final Callback cb = new Callback() {
public boolean handleMessage(Message msg) {
onJavaCallback((String) msg.obj);
return false;
}
};
paListaner = new PlatformActionListener() {
public void onComplete(Platform platform, int action, HashMap<String, Object> res) {
if (DEBUG) {
System.out.println("onComplete");
System.out.println(res == null ? "" : res.toString());
}
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("platform", ShareSDK.platformNameToId(platform.getName()));
map.put("action", action);
map.put("status", 1); // Success = 1, Fail = 2, Cancel = 3
map.put("res", res);
Message msg = new Message();
msg.obj = hashon.fromHashMap(map);
UIHandler.sendMessage(msg, cb);
}
… …
}
可以看出監(jiān)聽Complete事件的listener將message的處理都交給了cb,而cb調(diào)用了onJavaCallback方法。
onJavaCallback方法是jni導(dǎo)出的方法,它的實現(xiàn)在 AppDemo/Classes/C2DXShareSDK/Android/ShareSDKUtils.cpp里面。
JNIEXPORT void JNICALL Java_cn_sharesdk_ShareSDKUtils_onJavaCallback
(JNIEnv * env, jclass thiz, jstring resp) {
CCJSONConverter* json = CCJSONConverter::sharedConverter();
const char* ccResp = env->GetStringUTFChars(resp, JNI_FALSE);
CCLog("ccResp = %s", ccResp);
CCDictionary* dic = json->dictionaryFrom(ccResp);
env->ReleaseStringUTFChars(resp, ccResp);
CCNumber* status = (CCNumber*) dic->objectForKey("status"); // Success = 1, Fail = 2, Cancel = 3
CCNumber* action = (CCNumber*) dic->objectForKey("action"); // 1 = ACTION_AUTHORIZING, 8 = ACTION_USER_INFOR,9 = ACTION_SHARE
CCNumber* platform = (CCNumber*) dic->objectForKey("platform");
CCDictionary* res = (CCDictionary*) dic->objectForKey("res");
// TODO add codes here
if(1 == status->getIntValue()){
callBackComplete(action->getIntValue(), platform->getIntValue(), res);
}else if(2 == status->getIntValue()){
callBackError(action->getIntValue(), platform->getIntValue(), res);
}else{
callBackCancel(action->getIntValue(), platform->getIntValue(), res);
}
dic->autorelease();
}
這就是兩塊代碼的關(guān)鍵聯(lián)系。而問題似乎就出在onJavaCallback方 法里,因為我們看到了該方法中使用了Cocos2d-x的數(shù)據(jù)結(jié)構(gòu)類。
我們來看一下onJavaCallback方法是在哪個線程里執(zhí)行的。Cocos2d-x App至少有兩個線程,一個UI Thread(Activity),一個Render Thread。顯然onJavaCallback是在UI Thread中被執(zhí)行的。但是我們知道Cocos2d-x的AutoreleasePool是在Render Thread中管理的,并在幀切換時進行釋放操作的。
我們似乎聞到了問題的味道。Cocos2d-x基本上算是一個"單線程"游戲架構(gòu),所有的渲染操作、渲染樹節(jié)點邏輯管理、絕大多數(shù)游戲邏輯都在 Render Thread中進行,UI Thread更多的是接收系統(tǒng)事件,并傳遞給Render Thread處理。Cocos2d-x的內(nèi)存管理在這樣的“單線程”背景下是沒有大問題的,都是串行操作,不存在thread racing的情況。但一旦另外一個線程也調(diào)用內(nèi)存管理接口進行對象內(nèi)存操作時,問題就出現(xiàn)了,Cocos2d-x的內(nèi)存池管理不是線程安全的。
我們回到上面代碼,重點看一下json轉(zhuǎn)dic的方法,該方法將分享應(yīng)答字符串轉(zhuǎn)換為內(nèi)部的dictionary結(jié)構(gòu):
//AppDemo/Classes/C2DXShareSDK/Android/JSON/CCJSONConverter.cpp
CCDictionary * CCJSONConverter::dictionaryFrom(const char *str)
{
cJSON * json = cJSON_Parse(str);
if (!json || json->type!=cJSON_Object) {
if (json) {
cJSON_Delete(json);
}
return NULL;
}
CCAssert(json && json->type==cJSON_Object, "CCJSONConverter:wrong json format");
CCDictionary * dictionary = CCDictionary::create();
convertJsonToDictionary(json, dictionary);
cJSON_Delete(json);
return dictionary;
}
void CCJSONConverter::convertJsonToDictionary(cJSON *json, CCDictionary *dictionary)
{
dictionary->removeAllObjects();
cJSON * j = json->child;
while (j) {
CCObject * obj = getJsonObj(j);
dictionary->setObject(obj, j->string);
j = j->next;
}
}
CCObject * CCJSONConverter::getJsonObj(cJSON * json)
{
switch (json->type) {
case cJSON_Object:
{
CCDictionary * dictionary = CCDictionary::create();
convertJsonToDictionary(json, dictionary);
return dictionary;
}
case cJSON_Array:
{
CCArray * array = CCArray::create();
convertJsonToArray(json, array);
return array;
}
case cJSON_String:
{
CCString * string = CCString::create(json->valuestring);
return string;
}
case cJSON_Number:
{
CCNumber * number = CCNumber::create(json->valuedouble);
return number;
}
case cJSON_True:
{
CCNumber * boolean = CCNumber::create(1);
return boolean;
}
case cJSON_False:
{
CCNumber * boolean = CCNumber::create(0);
return boolean;
}
case cJSON_NULL:
{
CCNull * null = CCNull::create();
return null;
}
default:
{
CCLog("CCJSONConverter encountered an unrecognized type");
return NULL;
}
}
}
可以看出整個解析過程,都直接用的是傳統(tǒng)的Cocos2d-x對象構(gòu)造方法:create。在每個對象的create中,代碼都會調(diào)用該對象的 autorelease方法。而這個方法本身就是線程不安全的,且即便autorelease調(diào)用ok,在下一幀切換時,這些對象將都會被release 掉,如果在UI Thread中再引用這些對象的地址,那勢必造成內(nèi)存的非法訪問,而引發(fā)程序崩潰。
四、Fix方法
可能有朋友會問,create后,我retain一下可否?答案是否。因此create的創(chuàng)建不是線程安全的,create和retain兩個調(diào) 用之間存在時間差,而在這段時間內(nèi),該對象就有可能被render thread釋放掉。
Fix方法很簡單,就是在UI Thread中不使用Cocos2d-x的內(nèi)存管理機制,我們用傳統(tǒng)的new來替代create,并將 Java_cn_sharesdk_ShareSDKUtils_onJavaCallback最后的autorelease改為release,這樣就 不用勞煩Render Thread來幫我們釋放內(nèi)存了。CCDictionary的destructor調(diào)用時還會將Dictionarny內(nèi)部所有Element自動釋放掉。
- 在AndroidManifest.xml中uses-sdk內(nèi)屬性意思
- Android下如何使用百度地圖sdk
- Cocos2d-x 3.0中集成社交分享ShareSDK的詳細步驟和常見問題解決
- 微信公眾平臺開發(fā)接口PHP SDK完整版
- android開發(fā)環(huán)境搭建詳解(eclipse + android sdk)
- Intellij IDEA + Android SDK + Genymotion Emulator打造最佳Android開發(fā)環(huán)境
- Android SDK三種更新失敗及其解決方法
- php實現(xiàn)基于微信公眾平臺開發(fā)SDK(demo)擴展的方法
- Python Web框架Flask中使用七牛云存儲實例
- 通過Python來使用七牛云存儲的方法詳解
- 如何使用七牛Python SDK寫一個同步腳本及使用教程
相關(guān)文章
flutter實現(xiàn)掃碼槍獲取數(shù)據(jù)源禁止系統(tǒng)鍵盤彈窗示例詳解
這篇文章主要為大家介紹了flutter實現(xiàn)掃碼槍獲取數(shù)據(jù)源禁止系統(tǒng)鍵盤彈窗示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01Android Studio 代理配置指南(小結(jié))
這篇文章主要介紹了Android Studio 代理配置指南(小結(jié)),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-01-01Android啟動內(nèi)置APK和動態(tài)發(fā)送接收自定義廣播實例詳解
這篇文章主要介紹了Android啟動內(nèi)置APK和動態(tài)發(fā)送接收自定義廣播實例詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06Android仿淘寶view滑動至屏幕頂部會一直停留在頂部的位置
這篇文章主要介紹了Android仿淘寶view滑動至屏幕頂部會一直停留在頂部的位置的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-11-11CDC與BG-CDC的含義電容觸控學(xué)習(xí)整理
今天小編就為大家分享一篇關(guān)于CDC與BG-CDC的含義電容觸控學(xué)習(xí)整理,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12