【问题标题】:How to serialize a Bundle?如何序列化一个Bundle?
【发布时间】:2011-02-05 14:17:53
【问题描述】:

我想序列化一个 Bundle 对象,但似乎找不到一种简单的方法。使用 Parcel 似乎不是一种选择,因为我想将序列化的数据存储到文件中。

关于如何做到这一点的任何想法?

我想要这样做的原因是保存和恢复我的活动状态,即使它被用户杀死。我已经创建了一个带有我想要保存在 onSaveInstanceState 中的状态的 Bundle。但是android只有在activity被SYSTEM杀死时才会保留这个Bundle。当用户杀死活动时,我需要自己存储它。因此我想序列化并将其存储到文件中。当然,如果您有任何其他方式来完成同样的事情,我也会很感激。

编辑: 我决定将我的状态编码为 JSONObject 而不是 Bundle。然后可以将 JSON 对象作为 Serializable 放入 Bundle 中,或存储到文件中。可能不是最有效的方法,但它很简单,而且似乎工作正常。

【问题讨论】:

    标签: android serialization bundle


    【解决方案1】:

    我使用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】:

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

    享受吧!

    【讨论】:

    • 是的,我也发现了。问题是不能保证您可以再次对其进行解组,例如操作系统是否已更新并且 Parcel 是否已更改。但如果你能忍受,那就没问题了。
    • 从方法 mashall() 检索的数据不得放置在任何类型的持久存储中(在本地磁盘上、通过网络等)。为此,您应该使用标准序列化或另一种通用序列化机制。 Parcel marshalled 表示针对本地 IPC 进行了高度优化,因此不会尝试保持与平台不同版本中创建的数据的兼容性。 (developer.android.com/reference/android/os/…)
    • 我认为您不应该将保存的文件传输到其他设备,但如果您在单个设备上使用它可能没问题(例如用于保存临时数据)。
    【解决方案3】:

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

    【讨论】:

    • 这再次违背了将 Parcel 的内容存储到任何形式的持久内存中的建议(Javadocs 对此提出警告)。假设您出于某种原因去更新您的操作系统,那么上面的代码将在“restoreFromPreferences()”方法中使您的应用程序崩溃或在包中返回一些未知值。
    【解决方案4】:

    如果您想将其存储在持久存储中,则不能依赖可打包或可序列化机制。你必须自己做,下面是我通常做的方式:

    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 库(这很常见,只是为了让你知道)。

    【讨论】:

      猜你喜欢
      • 2021-12-31
      • 2014-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-05
      相关资源
      最近更新 更多