web-dev-qa-db-ja.com

バンドルをSharedPreferencesに保存

私はAndroidゲームのすべてのデータをsavedInstanceState Bundleに適合させるために、かなりの時間を費やしました。多くのParcelableオブジェクトを含む、完全に多くのデータがあります。これにより、アプリが一時停止または向きが変更された場合、アクティビティが再作成されてもデータは失われません。

ただし、最近、saveedInstanceStateバンドルが長期保存には不適切であることを発見しました。そこで、私は既存のsaveメソッドを長期的なソリューションとして機能するように適合させて、ゲームの状態を常に復元できる方法を探しています。

これまでに2つの解決策があると聞いています。

1)向きの変更にはsavedInstanceStateバンドルを使用しますが、アプリを完全にシャットダウンする必要がある場合はSharedPrefsも組み込みます。

基本的に同じことを行うために2つの異なる完全な方法を使用するため、これは信じられないほど逆効果です。また、私のsavedInstanceState BundleはParcelableオブジェクトを使用しているため、SharedPrefsに書き込めるようにするには、それらの各オブジェクトに別のメソッドを指定する必要があります。本質的に、複製された管理しにくいコードがたくさんあります。

2)savedInstanceStateバンドルをシリアル化し、ファイルに直接書き込みます。

私はこれに心を開いていますが、実際にそれを行う方法を知りません。ただし、Androidでのシリアル化は「コミカル/使用できないほど遅い」とのことですので、もっと良い解決策があるかもしれないとの希望を抱いています。

誰かが私にこれに対する解決策を提供してくれるなら、私は非常に感謝しています。

23
Dan

バンドルをSharedPreferencesに保存する半自動の方法である、この問題に対する独自の解決策を考え出しました。 Bundleの保存に必要な方法は1つだけですが、データを再度取得して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();
}

必要なタイプのケースのみを書いていることに注意してください。ただし、他のタイプも含むバンドルがある場合、これは簡単に適応できるはずです。

このメソッドは、指定されたバンドル内に格納されている他のバンドルオブジェクトを再帰的に保存します。ただし、Parcelableオブジェクトでは機能しないため、Parcelableオブジェクトを変更して、代わりにバンドルに格納する必要がありました。パーセルとバンドルはかなり似ているので、これはそれほど難しくありませんでした。残念ながら、バンドルは小包よりも少し遅いかもしれません。

次に、以前に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メソッドが同じ手法を使用するようになったため、代替コードよりもはるかに少ないコードで済みます。

4
Dan

おかしい、今週、47 - Android Weekly の問題47がこのライブラリを解き放ちました: Android complexプリファレンス

それはあなたに合うはずです。

18
Snicolas

バンドルからの回答を拡張して、バンドルを自動的に再作成する機能を追加し、名前が競合する可能性を低くしました。

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;
}
1
Neromancer