CCUserDefault lost data in android phone?

I have posted a game early this month on Google Play, and got about 100K downloads.

I use Google Protobuf to organize my data, serialize it to binary code and base64 as a text.
At last I use CCUserDefault::sharedUserDefault()->setStringForKey to save and call flush every time

But Many users commented: “Lose data , and game back to the start”.

Anybody can give me some suggestions?

FishingJoy1 also has this bug report, but we haven’t reproduce it.
Some one said remove the battery when playing the game can cause data losing, some one said fwrite method is not reliable enough.
So finally FishingJoy team move CCUserDefault from fwrite to android.content.SharedPreferences. You can refer to this doc http://developer.android.com/reference/android/content/SharedPreferences.html. This android sdk method covered all exceptions, it works very well that we haven’t received data losing report.
But this modification hasn’t been merged into cocos2d-x framework yet. You can call android.content.SharedPreferences via JNI from CCUserDefault. Perhaps we should create a issue for this.

BTW. What’s your game? 100K downloads is no bad. Could you give me an URL?

Thanks.
I will try this, and post the result later.

Having similar issues with Hill Climb Racing.

This issue got very rare by removing the flush call from destructor of CCUserDefault, as it tries to save progress when the .so gets unloaded and this would happen if user runs out of battery while playing or if the game got killed by a “task manager” when left running background. Instead we now save the progress only when changes happen.

Still it seems that there are some users experiencing this, I’m just wondering if the xml library calls fflush after it’s done writing with fwrite. Also thinking about using the Android shared preferences…

Got the same issues today while testing the new game, it seems the save method is unreliable.

Have you invoke CCUserDefault::flush() after you changing the content?

No, should I invoke it?

CCUserDefault::flush* is used to save the data to disk. If not invoked, all changes to the dat will not be saved to disk.
The name
flush()* is very misleading though. When I first saw it, I thought *flush()* would erase all data on the save file.

Thought I’d post an update on my experiences with Hill Climb Racing, even though we made those minor adjustments when the flush was called, users data still got corrupted sometimes and we had lots of support contacts regarding user save data being lost. We finally decided to try the SharedPreferences approach and the support contacts about corrupted data got reduced greatly after the update, so I recommend using the SharedPreferences if possible.

Never had this issue so far on 3 cocos2dx android games released.
I do agree from a design perspective it should use SharedPreferences API for Android implementation.

Hi Toni,
Thanks for the update, I’ve decided to use Android’s shared preference too for my next game.
Did you receive reports of data corruption on iOS too? My concern there this bug can also happen in iOS.

Hi Oren,

There’s been only one report on iOS and the user said it got corrupted when the battery run out while playing. Seemingly its very rare as the game has over 10M downloads on iOS also.

Is anyone able to provide some source for the JNI interface / Android side of things? I’m looking into doing this myself now, figure why reinvent the wheel if all you guys have already done it :wink:

Hi,

For JNI:

void setIntJNI(const char* key,int value)
{
    JniMethodInfo methodInfo;

    if (! getStaticMethodInfo(methodInfo, "setInt", "(Ljava/lang/String;I)V"))
    {           
        return;
    }

    jstring stringArg = methodInfo.env->NewStringUTF(key);
    int v=value;
    methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, stringArg,v);
    methodInfo.env->DeleteLocalRef(stringArg);
    methodInfo.env->DeleteLocalRef(methodInfo.classID);
}

int getIntJNI(const char* key,int value)
{
    JniMethodInfo methodInfo;

    if (! getStaticMethodInfo(methodInfo, "getInt", "(Ljava/lang/String;I)I"))
    {           
        return 0;
    }

    jstring stringArg = methodInfo.env->NewStringUTF(key);
    int v=value;
    int res=methodInfo.env->CallStaticIntMethod(methodInfo.classID, methodInfo.methodID, stringArg,v);
    methodInfo.env->DeleteLocalRef(stringArg);
    methodInfo.env->DeleteLocalRef(methodInfo.classID);

    return res;
}

UserDefaults wrapper:

void setInteger(const char* key,int value)
{
#ifdef ANDROID
    setIntJNI(key,value);
#else
    CCUserDefault::sharedUserDefault()->setIntegerForKey(key,value);
#endif
}

int getInteger(const char* key,int defaultValue)
{
#ifdef ANDROID
    return getIntJNI(key,defaultValue);
#else
    return CCUserDefault::sharedUserDefault()->getIntegerForKey(key,defaultValue);
#endif
}

On Android in your inherited Cocos2dxActivity:

public static SharedPreferences sharedPrefs;
public static Editor prefsEditor;

protected void onCreate(Bundle savedInstanceState)
{
     // ...

     sharedPrefs= getApplicationContext().getSharedPreferences("yourapp.preferences", Activity.MODE_PRIVATE);
     prefsEditor = sharedPrefs.edit();

     // ...
}

public static int getInt(String key, int defValue)
{
     try
     {
           int v = sharedPrefs.getInt(key, defValue);
           return v;
     }
     catch (Exception e)
     {
          return 0;
     }
}

public static void setInt(String key, int value)
{
     try
     {          
           prefsEditor.putInt(key, value);
           prefsEditor.commit();
     }
     catch (Exception e)
     {
     }
}

Of course this goes the same for the other data types you need.
Hope this helps.

Great, should be enough to get me going. Thanks Cristian!

An important thing to note when using JNI and share preferences is that if the call is made
from a different thread the JNI call will fail and won’t work.
Take that into account if you are using threads!

Hello friends, I’m new and I’m finishing my first game, I have some concerns:
I create my own file jni?
I can not find UserDefaults wrapper
  Thank you for your help.

I’ve faced the same problem with losting my saved data. So I’ve moved to JNI sharedPreferences. It works better but no so good as it should work. It losts data also but rarely than CCUserDefault. I’ve read sharePreferences at the start of my application so it never starts if saved data is corrupted. F**king XML! It’s a very big format just for storing a little data. So if you need to do commit operations very often - it brokes on Android (especially on slow devices). So it brokes all your game :slight_smile:

Has anyone solved such problem with sharedPreferences? By the way, CCUserDefault for iOS also uses XML nowdays? :slight_smile:

CCUserDefault works well for me, the problem is when the key is int to string ex: “1” on a for() or while() for a score board.
concatenating a letter solved

ex
for (int i = 0; i <9, i + +)
{
sprintf (var_i, “data_% d”, i);
if (data> CCUserDefault :: sharedUserDefault () -> getIntegerForKey (var_i)) // if score is greater
{
…………………….

i am facing the same problem

Utility::setUnlockedLevel(8);
CCUserDefault::sharedUserDefault()>flush;
and in utility
void Utility::setUnlockedLevel {
CCUserDefault *defaults = CCUserDefault::sharedUserDefault;
defaults
>setIntegerForKey(kUnlockedLevelKey, level);
CCUserDefault::sharedUserDefault()->flush();
}

int Utility::getUnlockedLevel(){
return CCUserDefault::sharedUserDefault()->getIntegerForKey(kUnlockedLevelKey);
}

but it is returning garbage value on android whereas correct value in iphone……. Can somebody tell me the problem