热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

ShareSDK造成App崩溃的一个BUG原因分析以及Fix方法

这篇文章主要介绍了ShareSDK造成App崩溃的一个BUG原因分析以及Fix方法,使用的是Cocos2d-x专用ShareSDK组件,需要的朋友可以参考下

近期研究了一下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 res) {
                if (DEBUG) {
                    System.out.println("onComplete");
                    System.out.println(res == null ? "" : res.toString());
                }
                HashMap map = new HashMap();
                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自动释放掉。


推荐阅读
  • 深入理解ExtJS:从入门到精通
    本文详细介绍了ExtJS的功能及其在大型企业前端开发中的应用。通过实例和详细的文件结构解析,帮助初学者快速掌握ExtJS的核心概念,并提供实用技巧和最佳实践。 ... [详细]
  • 在计算机技术的学习道路上,51CTO学院以其专业性和专注度给我留下了深刻印象。从2012年接触计算机到2014年开始系统学习网络技术和安全领域,51CTO学院始终是我信赖的学习平台。 ... [详细]
  • 360SRC安全应急响应:从漏洞提交到修复的全过程
    本文详细介绍了360SRC平台处理一起关键安全事件的过程,涵盖从漏洞提交、验证、排查到最终修复的各个环节。通过这一案例,展示了360在安全应急响应方面的专业能力和严谨态度。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 本文介绍如何使用阿里云的fastjson库解析包含时间戳、IP地址和参数等信息的JSON格式文本,并进行数据处理和保存。 ... [详细]
  • 本文详细介绍了Git分布式版本控制系统中远程仓库的概念和操作方法。通过具体案例,帮助读者更好地理解和掌握如何高效管理代码库。 ... [详细]
  • 最近团队在部署DLP,作为一个技术人员对于黑盒看不到的地方还是充满了好奇心。多次咨询乙方人员DLP的算法原理是什么,他们都以商业秘密为由避而不谈,不得已只能自己查资料学习,于是有了下面的浅见。身为甲方,虽然不需要开发DLP产品,但是也有必要弄明白DLP基本的原理。俗话说工欲善其事必先利其器,只有在懂这个工具的原理之后才能更加灵活地使用这个工具,即使出现意外情况也能快速排错,越接近底层,越接近真相。根据DLP的实际用途,本文将DLP检测分为2部分,泄露关键字检测和近似重复文档检测。 ... [详细]
  • 创建项目:Visual Studio Online 入门指南
    本文介绍如何使用微软的 Visual Studio Online(VSO)创建和管理开发项目。作为一款基于云计算的开发平台,VSO 提供了丰富的工具和服务,简化了项目的配置和部署流程。 ... [详细]
  • 本文详细介绍了如何在Python3环境中配置Appium1.4.6,并指导如何连接模拟器进行自动化测试。通过本文,您将了解从环境搭建到模拟器连接的完整流程。 ... [详细]
  • 本文介绍了如何使用Gradle和gdx-setup.jar工具来创建LibGDX项目,包括详细的步骤和注意事项,适合初学者和有经验的开发者。 ... [详细]
  • Flowable 6.6.0 表单引擎在Web应用中的集成与使用
    本文档提供了Flowable 6.6.0版本中表单引擎在Web应用程序中的配置和使用指南,包括表单引擎的初始化、配置以及在Web环境下的具体实现方法。 ... [详细]
  • MyEclipse技巧:高效生成toString方法
    本文将介绍如何在MyEclipse中快速且高效地生成toString方法,帮助开发者简化编码过程,提高开发效率。 ... [详细]
  • 基于Java的学生宿舍管理系统设计
    本论文探讨了如何利用Java技术设计和实现一个高效的学生宿舍管理系统。该系统旨在提高宿舍管理的效率,减少人为错误,同时增强用户体验。通过集成用户认证、数据管理和查询功能,系统能够满足学校宿舍管理的多样化需求。 ... [详细]
  • 基于Spring Boot的家政服务平台毕业设计项目(含源代码)
    本文档介绍了如何搭建和运行一个基于Spring Boot的家政服务平台,旨在为计算机专业学生提供毕业设计参考。项目涵盖了从环境配置到核心功能实现的全过程。 ... [详细]
  • Gradle复合构建详解
    自Gradle 3.3起,复合构建功能得以实现,这是一种能够整合其他独立构建的高级构建模式。本文将详细介绍复合构建与多项目构建的区别,以及如何在实际项目中应用复合构建。 ... [详细]
author-avatar
电信他爹_186
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有