I\'d like to serialize a Bundle object, but can\'t seem to find a simple way of doing it. Using Parcel doesn\'t seem like an option, since I want to store the serialized dat
Convert it to SharedPreferences:
private void saveToPreferences(Bundle in) {
Parcel parcel = Parcel.obtain();
String serialized = null;
try {
in.writeToParcel(parcel, 0);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.write(parcel.marshall(), bos);
serialized = Base64.encodeToString(bos.toByteArray(), 0);
} catch (IOException e) {
Log.e(getClass().getSimpleName(), e.toString(), e);
} finally {
parcel.recycle();
}
if (serialized != null) {
SharedPreferences settings = getSharedPreferences(PREFS, 0);
Editor editor = settings.edit();
editor.putString("parcel", serialized);
editor.commit();
}
}
private Bundle restoreFromPreferences() {
Bundle bundle = null;
SharedPreferences settings = getSharedPreferences(PREFS, 0);
String serialized = settings.getString("parcel", null);
if (serialized != null) {
Parcel parcel = Parcel.obtain();
try {
byte[] data = Base64.decode(serialized, 0);
parcel.unmarshall(data, 0, data.length);
parcel.setDataPosition(0);
bundle = parcel.readBundle();
} finally {
parcel.recycle();
}
}
return bundle;
}
In case you want to store it in persistent storage you can't rely on parcelable nor serializable mechanism. You have to do it by yourself and below is the way how I usually do it:
private static final Gson sGson = new GsonBuilder().create();
private static final String CHARSET = "UTF-8";
// taken from http://www.javacamp.org/javaI/primitiveTypes.html
private static final int BOOLEAN_LEN = 1;
private static final int INTEGER_LEN = 4;
private static final int DOUBLE_LEN = 8;
public static byte[] serializeBundle(Bundle bundle) {
try {
List<SerializedItem> list = new ArrayList<>();
if (bundle != null) {
Set<String> keys = bundle.keySet();
for (String key : keys) {
Object value = bundle.get(key);
if (value == null) continue;
SerializedItem bis = new SerializedItem();
bis.setClassName(value.getClass().getCanonicalName());
bis.setKey(key);
if (value instanceof String)
bis.setValue(((String) value).getBytes(CHARSET));
else if (value instanceof SpannableString) {
String str = Html.toHtml((Spanned) value);
bis.setValue(str.getBytes(CHARSET));
} else if (value.getClass().isAssignableFrom(Integer.class)) {
ByteBuffer b = ByteBuffer.allocate(INTEGER_LEN);
b.putInt((Integer) value);
bis.setValue(b.array());
} else if (value.getClass().isAssignableFrom(Double.class)) {
ByteBuffer b = ByteBuffer.allocate(DOUBLE_LEN);
b.putDouble((Double) value);
bis.setValue(b.array());
} else if (value.getClass().isAssignableFrom(Boolean.class)) {
ByteBuffer b = ByteBuffer.allocate(INTEGER_LEN);
boolean v = (boolean) value;
b.putInt(v ? 1 : 0);
bis.setValue(b.array());
} else
continue; // we do nothing in this case since there is amazing amount of stuff you can put into bundle but if you want something specific you can still add it
// throw new IllegalStateException("Unable to serialize class + " + value.getClass().getCanonicalName());
list.add(bis);
}
return sGson.toJson(list).getBytes(CHARSET);
}
} catch (Exception e) {
e.printStackTrace();
}
throw new IllegalStateException("Unable to serialize " + bundle);
}
public static Bundle deserializeBundle(byte[] toDeserialize) {
try {
Bundle bundle = new Bundle();
if (toDeserialize != null) {
SerializedItem[] bundleItems = new Gson().fromJson(new String(toDeserialize, CHARSET), SerializedItem[].class);
for (SerializedItem bis : bundleItems) {
if (String.class.getCanonicalName().equals(bis.getClassName()))
bundle.putString(bis.getKey(), new String(bis.getValue()));
else if (Integer.class.getCanonicalName().equals(bis.getClassName()))
bundle.putInt(bis.getKey(), ByteBuffer.wrap(bis.getValue()).getInt());
else if (Double.class.getCanonicalName().equals(bis.getClassName()))
bundle.putDouble(bis.getKey(), ByteBuffer.wrap(bis.getValue()).getDouble());
else if (Boolean.class.getCanonicalName().equals(bis.getClassName())) {
int v = ByteBuffer.wrap(bis.getValue()).getInt();
bundle.putBoolean(bis.getKey(), v == 1);
} else
throw new IllegalStateException("Unable to deserialize class " + bis.getClassName());
}
}
return bundle;
} catch (Exception e) {
e.printStackTrace();
}
throw new IllegalStateException("Unable to deserialize " + Arrays.toString(toDeserialize));
}
You represent data as byte array which you can easily store to file, send via network or store to sql database using ormLite as follows:
@DatabaseField(dataType = DataType.BYTE_ARRAY)
private byte[] mRawBundle;
and SerializedItem:
public class SerializedItem {
private String mClassName;
private String mKey;
private byte[] mValue;
// + getters and setters
}
PS: the code above is dependent on Gson library (which is pretty common, just to let you know).
I use SharedPreferences to get around that limitation, it uses the same putXXX() and getXXX() style of storing and retrieving data as the Bundle class does and is relatively simple to implement if you have used a Bundle before.
So in onCreate I have a check like this
if(savedInstanceState != null)
{
loadGameDataFromSavedInstanceState(savedInstanceState);
}
else
{
loadGameDataFromSharedPreferences(getPreferences(MODE_PRIVATE));
}
I save my game data to a Bundle in onSaveInstanceState(), and load data from a Bundle in onRestoreInstanceState()
AND
I also save game data to SharedPreferences in onPause(), and load data from SharedPreferences in onResume()
onPause()
{
// get a SharedPreferences editor for storing game data to
SharedPreferences.Editor mySharedPreferences = getPreferences(MODE_PRIVATE).edit();
// call a function to actually store the game data
saveGameDataToSharedPreferences(mySharedPreferences);
// make sure you call mySharedPreferences.commit() at the end of your function
}
onResume()
{
loadGameDataFromSharedPreferences(getPreferences(MODE_PRIVATE));
}
I wouldn't be surprised if some people feel this is an incorrect use of SharedPreferences, but it gets the job done. I have been using this method in all my games (nearly 2 million downloads) for over a year and it works.
storing any Parcelable to a file is very easy:
FileOutputStream fos = context.openFileOutput(localFilename, Context.MODE_PRIVATE);
Parcel p = Parcel.obtain(); // i make an empty one here, but you can use yours
fos.write(p.marshall());
fos.flush();
fos.close();
enjoy!