标签 微信 下的文章

ShareSDK Cocos2d-x专用组件的一个Bug

近期研究了一下Game App做社交分享,最后选择了ShareSDK来集成,不仅是因为ShareSDK支持国内外主流社交平台,更重要的是ShareSDK提供了专门的 cocos2d-x集成方案,有专门的文档代码Demo供开发者参考。

文档中提到了三种集成方式:纯Java方式、plugin-x方式以及Cocos2d-x专用组件方式,这里选择了ShareSDK Cocos2d-x专用组件(v2.3.7版本)的方式。按照文档中描述的步骤进行的相对顺利,在各个社交平台的appkey生效后,我们对demo app进行了测试,居然发现app经常随机性的崩溃,有时甚至是每次都崩溃,经过深入分析,发现这是ShareSDK Cocos2d-x专用组件的一个严重Bug,下面详细说明一下Bug的产生原因以及Fix方法。

一、App崩溃的场景和代码位置

发生崩溃的场景如下:
    App Demo中有一个"Share"按钮,点击该按钮,App Demo向已经授权的社交平台分享一些Test Content,而App Demo就在收到分享结果应答时发生了崩溃。

代码位置大致如下:

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

崩溃的位置大致就在回调shareResultHandler前后的某个位 置,比较随机。

二、现象分析

通过查看Eclipse logcat窗口的调试日志,我们发现一些规律,一些在“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前已经被释放了,然后后续被引用时触发内存非法访问。Cocos2d-x采用的是内存 计数的内存管理机制,在我的《Cocos2d-x内存管理-绕不过去的坎》一文中有描述。了解Cocos2d-x的内存管理机制是理解这个Bug 的前提条件。

三、原因分析

看来不得不挖掘一下ShareSDK组件的代码了。AppDemo中ShareSDK组件的代码分为两个部分:AppDemo/Classes /C2DXShareSDK和AppDemo/proj.android/src/cn/sharesdk。前者是C++代码,后面则是Java 代码,两者通过jni调用联系在一起。我们重点来找出分享应答返回来时的关键联系。

集成ShareSDK的Cocos2d-x程序会在主Activity的onCreate方法中调用ShareSDKUtils.prepare();

我们来看看prepare方法的实现:

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

    … …
}

可以看出监听Complete事件的listener将message的处理都交给了cb,而cb调用了onJavaCallback方法。

onJavaCallback方法是jni导出的方法,它的实现在 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();
}

这就是两块代码的关键联系。而问题似乎就出在onJavaCallback方 法里,因为我们看到了该方法中使用了Cocos2d-x的数据结构类。

我们来看一下onJavaCallback方法是在哪个线程里执行的。Cocos2d-x App至少有两个线程,一个UI Thread(Activity),一个Render Thread。显然onJavaCallback是在UI Thread中被执行的。但是我们知道Cocos2d-x的AutoreleasePool是在Render Thread中管理的,并在帧切换时进行释放操作的。

我们似乎闻到了问题的味道。Cocos2d-x基本上算是一个"单线程"游戏架构,所有的渲染操作、渲染树节点逻辑管理、绝大多数游戏逻辑都在 Render Thread中进行,UI Thread更多的是接收系统事件,并传递给Render Thread处理。Cocos2d-x的内存管理在这样的“单线程”背景下是没有大问题的,都是串行操作,不存在thread racing的情况。但一旦另外一个线程也调用内存管理接口进行对象内存操作时,问题就出现了,Cocos2d-x的内存池管理不是线程安全的。

我们回到上面代码,重点看一下json转dic的方法,该方法将分享应答字符串转换为内部的dictionary结构:

//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;
        }
    }
}

可以看出整个解析过程,都直接用的是传统的Cocos2d-x对象构造方法:create。在每个对象的create中,代码都会调用该对象的 autorelease方法。而这个方法本身就是线程不安全的,且即便autorelease调用ok,在下一帧切换时,这些对象将都会被release 掉,如果在UI Thread中再引用这些对象的地址,那势必造成内存的非法访问,而引发程序崩溃。

四、Fix方法

可能有朋友会问,create后,我retain一下可否?答案是否。因此create的创建不是线程安全的,create和retain两个调 用之间存在时间差,而在这段时间内,该对象就有可能被render thread释放掉。

Fix方法很简单,就是在UI Thread中不使用Cocos2d-x的内存管理机制,我们用传统的new来替代create,并将 Java_cn_sharesdk_ShareSDKUtils_onJavaCallback最后的autorelease改为release,这样就 不用劳烦Render Thread来帮我们释放内存了。CCDictionary的destructor调用时还会将Dictionarny内部所有Element自动释放 掉。

毕业九年 – 忆我的大学同学

又是一年毕业季。自从九年前坐车驶出母校大门,我就再也没有回过母校,确是十分怀念,但更是怀念那些一起生活了四年的大学同学们。刚毕业那会儿,与同学们 还都保持着联系。后来大家都有了自己的新圈子,渐渐的联系少了,甚至断了。最初的校友录也荒废了,QQ我早就不用了,于是乎与大家天各一方,各自发展。

上周五去北京开会,会后我的直接领导去见他的大学同学,我也被邀请同去了^_^(要么也是无聊地等火车)。饭桌上两位多年不见的老同学相见甚欢,那场面深深感染了我。在回沈的动车上我就下定决心:一定要找到组织^_^。

在移动互联网如此发达的今天,这件事还真的不难。在微信和微博这样优秀社交工具的帮助下,不出一天,我就顺利找到了组织(微信群),并初步了解到大家的情况。

没有聚会,胜似聚会。对我来说,能在微信群里看到大家,我已经很满足了。一晃9年过去了,同学早已分布在全国各地,至少一半已经结婚生子/ 女。有下一代的,微信的头像多是以下一代的照片(Me, too^_^)。看着群里面一个个微信头像,让我不由得忆起当年的大学生活,点点滴滴,如现眼前。以下对我的同学做逐一的回忆,印象是主观的,并且有些信息可能已经不靠谱了。

班级

哈工大,自动化测试与控制系,测控技术与仪器专业,本科0001110班(2000入学,2004毕业)。

成员

* 田玉博
班长,外号田鸡。哈尔滨本地人,似乎是省重点哈三中毕业的。人长的一副书生样,细高,感觉有些偏瘦弱,带着一副眼镜。入学时被导员任命为代理班长。后经民 选后正式转正,记得当初班级选班长时,我还得了一票,弄得我极其不好意思。大学四年,田同学展现了很好的组织能力,我们班在大一、大二的集体活动还是蛮多 蛮丰富的。大四第一次考研,班长似乎发挥欠佳,后复读重考一举中的。目前在北京某企业工作,发展的相当不错。

* 岳晓帆
学习委员。哈尔滨本地人。据说和我们并不是一届的学生。是上一届实验学院的。后由于不堪实验学院高压,来到我们专业重新来过的。由于岳童鞋基本不住寝室, 所以与他接触不多。体育课让我们大家可以相互了解,因为我们报的都是足球班。平时以聊足球、踢足球为共同嗜好。印象里他的另一嗜好就是看电影了,与我们班 大黄(DVD发烧友)交往慎密。现状不知。

* 毛定涛
我们班最神秘的人。记忆中是来自湖北,他是我们班入学时的最高分。我们见过一面后,他就去新加坡南洋理工做交换生了。此后再无音信,现状未知。

* 方运
外号小方。江西九江人,是我们班年龄最小的童鞋,人长得也是瘦小瘦小的,因此平时是我们重点的欺负对象^_^。但小方的学习成绩却是我班最好的,毕业时因 在系里成绩名列前茅而被保研了。并且似乎是目前我所知班里唯一读到博的。目前刚毕业没几年,在苏州从事显示相关元件的研发工作。小方是我毕业后唯一见过面 的同学,那还是在刚毕业后没几年,小方从沈阳换火车,我们一起吃了顿饭(时间有限)。

* 杨云良
外号:杨老大。至少我们都这么叫,也许是其年龄比我们大点的缘故吧。杨老大让我记忆最深的事情是有一年冬天,老大在滑冰课上门牙摔掉了,那个寒假杨老大没 有回家。我也因某种原因没有回家,于是我们就算是做个伴儿,每天盒饭伺候,也不亦乐乎。杨老大毕业后就去南方工作了。现在在苏州,已成家立业了。结婚那 天,好多在华南、华东一带的同学都参加了老大的婚礼,我远在北方没能见证。

* 林龙
福建人,样子涨的也很福建。让我印象最深的就是他是个聪明孩儿。考研复习其间也是边玩游戏边复习,居然考上上海交大了,自愧差的太远啊。现居上海。

* 姚小勇 ,湖北人,高考成绩在班里也是名列前茅的。但到大学后有些“堕落”,迷于游戏,成为游戏男。毕业后似乎也在华东一带,校友录上他的最新照片显示的是在国电新疆工作

* 黄易廷
浙江人。长像挺“猥琐”^_^。我们都管他叫“大黄”。电影/DVD骨灰粉,零花钱都买DVD了,人家衣柜里都是衣服,他衣柜里都是各种盒装的、纪念版的 DVD。他和前面提到的岳晓帆在这方面很对路。另外他还有一个爱好,那就是听古典音乐。他的床边放着一个音乐播放器,每天他似乎都是伴着贝多芬、肖邦的音 乐入眠的。目前大黄在浙江杭州工作,居然和我是同行,万万没有想到啊。

* 邓立宝
记得没错应该是河北人,我就管他叫“宝哥”。宝哥为人低调,后来做家教时居然处了个本地的女朋友(似乎是我们学姐),让我们着实有些吃惊。印象中比较深刻的是经常和宝哥去打乒乓球。毕业后,宝哥似乎是考上研究生了,目前没有他的信息。

下面七个都是我们寝室的兄弟了。

* 郭朋杰
河南人。大学入学时,第一个遇到的就是他,之后总是一起去吃饭,到街上买东西等。印象中,他喜欢直来直去,而且似乎是急性子。考研复习其间搬出去租房子了。后考上了研究生,毕业后似乎是在成飞,保密单位。目前在成都安家了,也有了孩子。

* 柴保明
河南新乡汉子,身材魁梧。高中时的程序高手。到大学后不知为何不愿在计算机编程方面再做投入了。喜看书。后也上了游戏瘾,玩了一阵。考研期间戒掉。研究生是在复旦读的。毕业后留在上海,在摩根士丹利做开发工程师。现状估计也过得不错吧。

* 胡士杰
外号小木,也是他自己喜欢的昵称。成都人,游戏男。玩游戏,爱游戏,现在在成都创业做游戏,据说有风投。在我看来,他不做游戏就白瞎了。他父母都是工程 师,家境不错,印象中胡士杰也很聪明。不过最深的印象还是他有些小洁癖,如果后半夜你能在洗漱间看到人影,那多半是他。他那双手每天晚上不知道要洗上多少 遍。他的床铺是不允许别人坐的,其实坐了也没告诉他^_^。毕业后他现在山东浪潮软件,后估计就是回老家创业去了。

* 魏陈
外号老木^_^。上海小资男。大三就泡了个mm出去住了,毕业后回了老家,目前工作保密,靠,居然不告诉我。我可是你曾经上铺的兄弟啊。

* 高一鹏
魏陈搬出去后,我的另一个下铺兄弟。阿城人,黑龙江壮男。为人忠厚老实,与人为善,脸上总是挂着笑容。毕业后应该是重新考研了,现在应该还在中兴,至于哪个城市未知。让我感到印象最深的是他的路似乎都是他老爹替他安排好的。

* 庞鸿光
广州斗门人。南方高又帅。对电子产品甚是钻研,我们班唯一参加大学挑战杯电子大赛的。做事十分投入。目前在深圳安家,取妻生女了,在中兴做芯片,也算“投其所好”了。

* 汪海龙
黑龙江伊春人,我管他就叫“龙”。平时十分要好,另外他在计算机系的高中女同学与我在计算机系的高中女同学是一班的,有时候一起活动常见面,再加上我平时 总喜欢旁听计算机系的课程,抬头不见低头见,互相都认识。龙最让我吃惊的是研究生入学考试数学居然考到140以上,一万个没想到啊。现在他定居深圳,服务 于华为。刚刚让老婆怀上宝宝,处于准爸爸角色。

* 张伟峰
吉林长春人。一起踢球喝酒的好兄弟。毕业后回长春了,现在应该是我们班最NB的了吧。自己创业,身价千万。更是没想到啊。

* 许晓明
好像是安徽人。肥头大耳,人长得挺可爱的,胖嘟嘟的,我没事就取笑其为“猪猪”,大二之后也沉溺于游戏。毕业后就工作了,其间到沈阳来过多次,可惜我都出差了。目前上海某工厂做质量保证,估计是个小头头。

* 孙明
江苏人,年纪小,但个头不小。同样也是一个游戏男、盒饭男(玩得没空去食堂吃饭)。毕业后也到华东一带工作了,具体信息不详。

* 许大怀 
体育委员,家是哈尔滨的,长的也很哈尔滨,眼睛很大。穿着时尚。由于经常不住寝室,总见不到人影,因此在大学时接触不多。最深的印象就是听说他姐和姐夫都是哈工大博士,很NB的。毕业后最后一次跟他联系时,他在东莞联通。

* 崔晓萌
同样哈尔滨人,典型的哈尔滨帅锅。接触也不多,也是因为常见不到人影。现状不详。

* 杨栋新
山西人。外号:大猩猩。好像是我给起的。一来新猩两字挺谐音,二来其相貌也神似^_^。这童鞋似乎不那么合群,四年了和我们大家的交流都很少。毕业后就不知去向了。

下面是我们班的8个mm的印象:

* 杜英
陕西人,忘记了是否是米脂的。人长的不错,就是黑了点。说话细声细气儿的。记得我们刚入学金工实习时,我因为总喜欢说“咱们”而被她批评无数次 – 是“我们”,不是“咱们”,不包括我。毕业后去了上海,目前也在上海工作生活。

* 王海霞
敦实的河南妹子,胖乎乎的,笑起来咪咪眼,着实可爱。现在想起来很还亲切呢。她算是当时这几个女同学中和我交流最多的女生了吧。研究生毕业,目前在上海工作。

* 张略
哈市本地美女,长辫子,白净净的。我和她接触很少。毕业后考上了研究生。现状未知。

* 池楠
黑龙江人,我们班的支书。身体魁梧壮实。外语超好,毕业后就去外交部了,工作后发出无数照片(什么迪拜塔等),游览世界各地明胜,让我们这干人羡慕不已。目前依旧在北京外交部某部门。

* 王婧婧
长的挺小巧的,少言寡语的。当初接触也不多。毕业后也考上研究生了,目前在浙江杭州,已当妈了,细节不知。

* 汤珺
广西柳州人,如果不考虑身高,算是个小美女了。如果算上身高,她是我们班最小的人了。很开朗活泼。大学时我们有过很多交流。毕业后应该也考上研究生了。目前定居上海,也已经是当妈的人了。

* 陈亦能
长像略有些粗犷的绍兴人,和鲁迅是同乡。大学时沟通不多,目前她在上海(微信上)。

* 姜岚
黑龙江人。大学时沟通也不多,似乎挺会包饺子的(大一元旦活动)。现状未知。

九年了,就算把这些童鞋的名字想完整也甚是不易。因此把九年后记忆中的同学印象写下来,也算是一种拯救。以后闲遐时回顾一下,偷着乐乐,也别有一番趣味。生活就是如此,去体会去感受才有意义。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言进阶课 AI原生开发工作流实战 Go语言精进之路1 Go语言精进之路2 Go语言第一课 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com
这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats