Как сериализовать Bundle?
Я хотел бы сериализовать объект Bundle, но не могу найти простой способ сделать это. Использование Parcel не похоже на вариант, так как я хочу сохранить сериализованные данные в файл.
Есть идеи о том, как это сделать?
Причина, по которой я хочу это, заключается в том, чтобы сохранять и восстанавливать состояние моей активности, в том числе и после того, как оно было уничтожено пользователем. Я уже создаю Bundle с состоянием, которое хочу сохранить в onSaveInstanceState. Но android сохраняет этот пакет только тогда, когда активность убита СИСТЕМОЙ. Когда пользователь убивает действие, я должен сохранить его сам. Поэтому я хотел бы сериализовать и сохранить его в файл. Конечно, если у вас есть какой-то другой способ сделать то же самое, я был бы благодарен за это тоже.
Изменить: я решил закодировать мое состояние как JSONObject вместо Bundle. Затем объект JSON может быть помещен в Bundle как сериализуемый или сохранен в файл. Вероятно, не самый эффективный способ, но он прост и, кажется, работает нормально.
4 ответа
Сохранить любой Parcelable в файл очень просто:
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();
наслаждаться!
Преобразуйте его в 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;
}
Я использую SharedPreferences, чтобы обойти это ограничение, он использует тот же стиль putXXX() и getXXX() для хранения и извлечения данных, что и класс Bundle, и относительно прост в реализации, если вы уже использовали Bundle.
Так в onCreate у меня есть проверка, как это
if(savedInstanceState != null)
{
loadGameDataFromSavedInstanceState(savedInstanceState);
}
else
{
loadGameDataFromSharedPreferences(getPreferences(MODE_PRIVATE));
}
Я сохраняю свои игровые данные в Bundle в onSaveInstanceState () и загружаю данные из Bundle в onRestoreInstanceState ()
А ТАКЖЕ
Я также сохраняю данные игры в SharedPreferences в onPause () и загружаю данные из SharedPreferences в 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));
}
Я не удивлюсь, если некоторые люди сочтут, что это неправильное использование SharedPreferences, но оно выполняет свою работу. Я использую этот метод во всех моих играх (около 2 миллионов загрузок) более года, и он работает.
В случае, если вы хотите сохранить его в постоянном хранилище, вы не можете полагаться на парсерализуемый или сериализуемый механизм. Вы должны сделать это самостоятельно, и ниже приведен способ, которым я обычно делаю это:
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));
}
Вы представляете данные в виде байтового массива, который вы можете легко сохранить в файл, отправить по сети или сохранить в базе данных sql с помощью ormLite следующим образом:
@DatabaseField(dataType = DataType.BYTE_ARRAY)
private byte[] mRawBundle;
и SerializedItem:
public class SerializedItem {
private String mClassName;
private String mKey;
private byte[] mValue;
// + getters and setters
}
PS: приведенный выше код зависит от библиотеки Gson (что довольно распространено, просто чтобы вы знали).