如何对 Bundle 进行序列化?

21
我想对一个Bundle对象进行串行化,但似乎找不到简单的方法。使用Parcel似乎不是一个选项,因为我想将序列化的数据存储到文件中。
有没有任何想法可以做到这一点?
我之所以想要这样做,是为了保存和恢复我的活动状态,即使它被用户杀死。我已经在onSaveInstanceState中创建了一个Bundle,并将其保存在其中。但是当用户杀死活动时,Android仅保留此Bundle。我需要自己存储它。因此,我想将其串行化并存储到文件中。当然,如果您有任何其他实现相同功能的方法,我也会感激。
编辑: 我决定将我的状态编码为JSONObject而不是Bundle。然后,可以将JSON对象作为Serializable放入Bundle中,或将其存储到文件中。可能不是最有效的方法,但它很简单,而且似乎工作得很好。
4个回答

7

将任何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();

请享受!


9
是的,我也发现了这个问题。问题在于不能保证您可以再次解组它,比如如果操作系统更新并且 Parcel 已更改。但是,如果您可以接受这一点,那就没问题了。 - hermo
14
从方法mashall()检索的数据不得放置在任何形式的持久性存储中(例如本地磁盘、网络等)。为此,您应该使用标准序列化或其他类型的通用序列化机制。Parcel marshalled表示法针对本地IPC进行高度优化,因此不会尝试与在平台的不同版本中创建的数据保持兼容性。 - Oneiros
我认为你不应该将保存的文件传输到其他设备,但如果你只在单个设备上使用它(例如用于保存临时数据),那么可能是可以的。 - Dmitry Zaytsev

6

我使用SharedPreferences来解决这个限制问题,它使用与Bundle类相同的putXXX()和getXXX()方式存储和检索数据,并且如果您之前使用过Bundle,则实现起来相对简单。

因此,在onCreate中,我的检查如下所示:

if(savedInstanceState != null)
{
    loadGameDataFromSavedInstanceState(savedInstanceState);
}
else
{
    loadGameDataFromSharedPreferences(getPreferences(MODE_PRIVATE));
}

我在onSaveInstanceState()方法中将游戏数据保存到Bundle中,在onRestoreInstanceState()方法中从Bundle中加载数据。

同时,我还在onPause()方法中将游戏数据保存到SharedPreferences中,在onResume()方法中从SharedPreferences中加载数据。

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的错误用法,但它能完成任务。我在所有我的游戏中使用这种方法(近200万下载)已经一年多了,并且它可行。


2
当然可以,我只是希望避免有两种捆绑状态的方式,即使它们非常相似。 - hermo
这正是我想要实现持久状态保存的方式。 - Awemo

6

将其转换为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;
}

6
这又违反了将 Parcel 的内容存储到任何形式的持久性存储中的建议(Javadocs 警告)。比如你因为某些原因更新了你的操作系统,那么上面的代码将会在“restoreFromPreferences()”方法中导致你的应用程序崩溃或返回一些未知的 bundle 值。 - Yinzara

0

如果您想将其存储在持久性存储中,则不能依赖于parcelable或serializable机制。 您必须自己完成,并且以下是我通常的方法:

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));
}

你可以将数据表示为字节数组,然后使用ormLite按照以下方式轻松地将其存储到文件、通过网络发送或存储到SQL数据库中:

    @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库(这是相当常见的,只是提醒一下)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接