Recently studied game app do social sharing, finally chose Sharesdk to integrate, not only because SHARESDK support the mainstream social platform at home and abroad, but more importantly, SHARESDK provides a special Cocos2d-x integration program, There is a special documentation and code demo for developers to refer to.
The documentation mentions three ways to integrate: pure Java, plugin-x, and cocos2d-x-specific components, where SHARESDK cocos2d-x specialized components (v2.3.7 versions) are selected. According to the steps described in the document, we tested the demo app after the appkey of each social platform came into effect, and discovered that the app's random crashes, sometimes crashes, were analyzed and found to be SHARESDK Cocos2d-x a serious bug in the private component, here is a detailed description of the cause of the bug and the Fix method.
First, the app crash scene and code location
The scenario where the crash occurred is as follows:
App Demo has a "Share" button, click on the button, app demo to the authorized social platform to share some test Content, and the app demo on the share of the answer to the results of the crash occurred.
The code position is roughly as follows:
Copy Code code as follows:
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
}
}
The location of the crash is roughly at a certain location before and after the callback Shareresulthandler, which is relatively random.
Second, the phenomenon analysis
By looking at the debug logs of the Eclipse Logcat window, we found some patterns, some of which were printed following the "Share OK Crash:"
Copy Code code as follows:
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 (SIGSEGV) at 0x00000003 (code=1), thread 1829 (Thread-122)
Guess what, it seems, is that some ccobject has been released before the real release, and then the subsequent reference triggers the memory illegal access. Cocos2d-x uses memory-counting memory management mechanisms that are described in my article, "Cocos2d-x memory management-the hurdle of the past." Understanding the Cocos2d-x memory management mechanism is a prerequisite for understanding this bug.
Iii. Analysis of Causes
It seems that I have to dig up the code for the SHARESDK component. The code for the SHARESDK component in Appdemo is divided into two parts: Appdemo/classes/c2dxsharesdk and APPDEMO/PROJ.ANDROID/SRC/CN/SHARESDK. The former is C + + code, followed by Java code, the two are linked through the JNI call. We focus on finding the key link when the shared response returns.
An integrated SHARESDK cocos2d-x program invokes Sharesdkutils.prepare () in the OnCreate method of the main activity;
Let's look at the implementation of the Prepare method:
Copy Code code as follows:
Appdemo/proj.android/src/cn/sharesdk/sharesdkutils.java
public class Sharesdkutils {
private static Boolean DEBUG = true;
private static 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 (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);
msg = new Message ();
Msg.obj = Hashon.fromhashmap (map);
Uihandler.sendmessage (MSG, CB);
}
... ...
}
Can be seen listening to the complete event listener will message processing to the CB, and CB called the Onjavacallback method.
The Onjavacallback method is a method of JNI derivation, which is implemented in Appdemo/classes/c2dxsharesdk/android/sharesdkutils.cpp.
Copy Code code as follows:
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 ();
}
This is the key connection to the two-block code. And the problem seems to be in the Onjavacallback method, because we see that the method uses the COCOS2D-X data structure class.
Let's take a look at which thread the Onjavacallback method executes. The Cocos2d-x app has at least two threads, a UI thread (activity), and a render thread. Obviously Onjavacallback was executed in UI thread. However, we know that the Cocos2d-x Autoreleasepool is managed in Render thread and is released when the frame is switched.
We seem to smell the problem. Cocos2d-x is basically a "single-threaded" game architecture, all rendering operations, rendering tree node logic management, most of the game logic in the Render thread, UI thread is more to receive system events, and passed to Render thread processing. Cocos2d-x memory management in such a "single-threaded" context is not a big problem, are serial operations, there is no thread racing situation. But once another thread calls the memory management interface for object memory operations, the problem arises, and cocos2d-x memory pool management is not thread-safe.
Let's go back to the code and focus on the JSON-to-dic approach, which converts the shared answer string into an internal dictionary structure:
Copy Code code as follows:
Appdemo/classes/c2dxsharesdk/android/json/ccjsonconverter.cpp
Ccdictionary * Ccjsonconverter::d ictionaryfrom (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;
}
}
}
We can see that the whole parsing process is directly used in the traditional Cocos2d-x object construction method: Create. In each object's create, the code calls the object's Autorelease method. This method itself is thread-insecure, and even if autorelease calls OK, the next frame switch, these objects will be release, if the object in the UI thread to refer to the address of these objects, it is bound to cause memory illegal access, and cause the program crashes.
Four, Fix method
May have a friend to ask, after the create, I retain can? The answer is yes. The creation is therefore not thread-safe, and there is a time lag between the create and retain two calls, and during this period the object may be released by render thread.
The Fix method is simple: instead of using the COCOS2D-X memory management mechanism in UI thread, we use the traditional new to replace the Create, and the Java_cn_sharesdk_sharesdkutils_ Onjavacallback the final autorelease to release, so you don't have to bother render thread to help us free up memory. The Ccdictionary destructor call also automatically frees all the element inside the Dictionarny.