将Bundle保存到SharedPreferences。

24

我花费了很大的力气使得我的Android游戏的所有数据都适应于savedInstanceState Bundle。一共有很多数据,包括许多可 Parcelable 对象。这确保了当应用程序暂停或方向改变时,通过重新创建 Activity,不会丢失任何数据。

然而,最近我才发现 savedInstanceState bundle 显然不适合长期存储。因此,我正在寻找一种方法来适应我的现有保存方法,以便作为长期解决方案工作,以便始终可以还原游戏状态。

到目前为止,我听说过两种解决方案:

1) 在方向更改时使用 savedInstanceState bundle,但在完全关闭应用程序时也结合使用 SharedPrefs。

这似乎非常低效,因为它使用了两种完全不同的方法来做基本上相同的事情。此外,由于我的 savedInstanceState Bundle 使用 Parcelable 对象,我将不得不为每个对象提供另一种方法,以使它们能够被写入 SharedPrefs。实际上是大量重复的和难以管理的代码。

2) 序列化 savedInstanceState Bundle 并直接将其写入文件。

我对此持开放态度,但我实际上不知道该怎么做。然而,我仍然抱着希望,因为我听说在 Android 中序列化是“可笑的/无法使用的缓慢”。

如果有人能够为我提供解决方案,我将非常感激。


只需查找序列化类即可进行序列化,应该不难找到。我在使用它时没有注意到任何极长的延迟。 - mango
我能找到的唯一信息告诉我需要实现Serializable接口,但Bundle并没有实现这个接口。 - Dan
我推荐使用 https://github.com/iamironz/binaryprefs 这个库,它允许通过实现 Persistable 接口(jdk 中的 Externalizable 接口)来像标准 Java 一样保存数据。 - denis.sugakov
3个回答

16

这似乎很有前途,但唉,我无法让它工作。我尝试过各种Bundle对象,包括空的对象和一些更简单的对象,比如Points,但仍然没有运气。当保存时,它要么抱怨“循环引用”,要么抱怨“存储在键___下的对象是另一个游戏的实例”时加载。这让我疯了... - Dan
请将此作为单独的问题发布。如果您在我的地址下添加评论,我会感兴趣关注它。 - Snicolas
1
确实,只是使用GSon将所有内容保存为JSon... 无论如何,我感觉你的数据可能是某个类的内部类。这会很容易地形成循环。你的POJO类是分开的吗? - Snicolas
这是新的问题链接:http://stackoverflow.com/questions/13665389/saving-objects-with-android-complexpreferences-gson 顺便感谢你的帮助。另外,在我之前的评论中,我写了“instanceof another game”,实际应该是“instanceof another class”。 - Dan

4
我现在想出了自己的解决方案,这是一种半自动化的将Bundles保存到SharedPreferences中的方法。我说半自动化是因为,虽然只需要一个方法就可以保存Bundle,但是再次检索数据并将其转换回Bundle需要一些工作。
以下是保存Bundle的代码:
SharedPreferences save = getSharedPreferences(SAVE, MODE_PRIVATE);
Editor ed = save.edit();
saveBundle(ed, "", gameState);

/**
 * Manually save a Bundle object to SharedPreferences.
 * @param ed
 * @param header
 * @param gameState
 */
private void saveBundle(Editor ed, String header, Bundle gameState) {
    Set<String> keySet = gameState.keySet();
    Iterator<String> it = keySet.iterator();

    while (it.hasNext()){
        key = it.next();
        o = gameState.get(key);
        if (o == null){
            ed.remove(header + key);
        } else if (o instanceof Integer){
            ed.putInt(header + key, (Integer) o);
        } else if (o instanceof Long){
            ed.putLong(header + key, (Long) o);
        } else if (o instanceof Boolean){
            ed.putBoolean(header + key, (Boolean) o);
        } else if (o instanceof CharSequence){
            ed.putString(header + key, ((CharSequence) o).toString());
        } else if (o instanceof Bundle){
            saveBundle(header + key, ((Bundle) o));
        }
    }

    ed.commit();
}

请注意,我只为我需要的类型编写了案例,但如果您的Bundle还包括其他类型,则应该很容易进行适应。
此方法将递归保存存储在给定Bundle中的其他Bundle对象。但是,它不适用于Parcelable对象,因此我不得不修改我的Parcelable对象以使它们将自己存储到Bundle中。由于Parcel和Bundle非常相似,这并不太难。不幸的是,我认为Bundles可能比Parcels稍微慢一些。
然后,我在我以前的所有Parcelable对象中编写了构造函数,使它们能够重新从存储在SharedPreferences中的数据中重新打包自己。重构所需数据的键很容易。假设您有以下数据结构:
Bundle b {
    KEY_X -> int x;
    KEY_Y -> Bundle y {
                 KEY_Z -> int z;
             }
}

这些将按以下方式保存到SharedPreferences中:
KEY_X -> x
KEY_YKEY_Z -> z

这可能不是世界上最漂亮的方法,但它有效,并且比可选方案少了很多代码。现在我的onSaveInstanceState方法和onPause方法使用相同的技术。


在这种情况下,我们如何获取Bundle? - Nam Vu
1
我不确定你的意思是什么...一旦Bundle已保存到SharedPrefs中,它就可以像任何其他Bundle一样被检索。 - Dan
关于编程的内容翻译:key和o是什么?你是从哪里传递这些参数的? - ekjyot
请问您能告诉我如何从共享首选项中获取Bundle吗? - ekjyot
抱歉!我已经丢失了原始代码,但我已经编辑了帖子,并尽力猜测它应该是什么样子。要从SharedPreferences检索Bundle,您必须手动重新创建键,如我帖子底部所述,然后使用这些键来检索所需内容。我开始认为将Bundle保存到SharedPreferences中并不是一个明智的想法 :) - Dan

2

我在Dan的回答基础上编写了一个函数,用于自动重新创建Bundle,并使名称不太可能发生冲突。

private static final String SAVED_PREFS_BUNDLE_KEY_SEPARATOR = "§§";

/**
 * Save a Bundle object to SharedPreferences.
 *
 * NOTE: The editor must be writable, and this function does not commit.
 *
 * @param editor SharedPreferences Editor
 * @param key SharedPreferences key under which to store the bundle data. Note this key must
 *            not contain '§§' as it's used as a delimiter
 * @param preferences Bundled preferences
 */
public static void savePreferencesBundle(SharedPreferences.Editor editor, String key, Bundle preferences) {
    Set<String> keySet = preferences.keySet();
    Iterator<String> it = keySet.iterator();
    String prefKeyPrefix = key + SAVED_PREFS_BUNDLE_KEY_SEPARATOR;

    while (it.hasNext()){
        String bundleKey = it.next();
        Object o = preferences.get(bundleKey);
        if (o == null){
            editor.remove(prefKeyPrefix + bundleKey);
        } else if (o instanceof Integer){
            editor.putInt(prefKeyPrefix + bundleKey, (Integer) o);
        } else if (o instanceof Long){
            editor.putLong(prefKeyPrefix + bundleKey, (Long) o);
        } else if (o instanceof Boolean){
            editor.putBoolean(prefKeyPrefix + bundleKey, (Boolean) o);
        } else if (o instanceof CharSequence){
            editor.putString(prefKeyPrefix + bundleKey, ((CharSequence) o).toString());
        } else if (o instanceof Bundle){
            savePreferencesBundle(editor, prefKeyPrefix + bundleKey, ((Bundle) o));
        }
    }
}

/**
 * Load a Bundle object from SharedPreferences.
 * (that was previously stored using savePreferencesBundle())
 *
 * NOTE: The editor must be writable, and this function does not commit.
 *
 * @param sharedPreferences SharedPreferences
 * @param key SharedPreferences key under which to store the bundle data. Note this key must
 *            not contain '§§' as it's used as a delimiter
 *
 * @return bundle loaded from SharedPreferences
 */
public static Bundle loadPreferencesBundle(SharedPreferences sharedPreferences, String key) {
    Bundle bundle = new Bundle();
    Map<String, ?> all = sharedPreferences.getAll();
    Iterator<String> it = all.keySet().iterator();
    String prefKeyPrefix = key + SAVED_PREFS_BUNDLE_KEY_SEPARATOR;
    Set<String> subBundleKeys = new HashSet<String>();

    while (it.hasNext()) {

        String prefKey = it.next();

        if (prefKey.startsWith(prefKeyPrefix)) {
            String bundleKey = StringUtils.removeStart(prefKey, prefKeyPrefix);

            if (!bundleKey.contains(SAVED_PREFS_BUNDLE_KEY_SEPARATOR)) {

                Object o = all.get(prefKey);
                if (o == null) {
                    // Ignore null keys
                } else if (o instanceof Integer) {
                    bundle.putInt(bundleKey, (Integer) o);
                } else if (o instanceof Long) {
                    bundle.putLong(bundleKey, (Long) o);
                } else if (o instanceof Boolean) {
                    bundle.putBoolean(bundleKey, (Boolean) o);
                } else if (o instanceof CharSequence) {
                    bundle.putString(bundleKey, ((CharSequence) o).toString());
                }
            }
            else {
                // Key is for a sub bundle
                String subBundleKey = StringUtils.substringBefore(bundleKey, SAVED_PREFS_BUNDLE_KEY_SEPARATOR);
                subBundleKeys.add(subBundleKey);
            }
        }
        else {
            // Key is not related to this bundle.
        }
    }

    // Recursively process the sub-bundles
    for (String subBundleKey : subBundleKeys) {
        Bundle subBundle = loadPreferencesBundle(sharedPreferences, prefKeyPrefix + subBundleKey);
        bundle.putBundle(subBundleKey, subBundle);
    }


    return bundle;
}

访问 https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/src-html/org/apache/commons/lang/StringUtils.html 以了解 StringUtils 函数的作用(Android 中未包含此内容)。 - James Alvarez

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