Сохранить пакет в SharedPreferences

Я очень занялся тем, чтобы все данные для моей игры в Android вписывались в пакет savedInstanceState Bundle. Там много данных вообще, включая множество объектов Parcelable. Это гарантирует, что когда приложение приостановлено или изменится ориентация, никакие данные не будут потеряны при восстановлении Деятельности.

Тем не менее, я только недавно обнаружил, что пакет savedInstanceState, по-видимому, не подходит для долговременного хранения. Поэтому я ищу способ, которым я могу адаптировать существующий метод сохранения, чтобы работать как долгосрочное решение, так что состояние игры всегда можно восстановить.

До сих пор я слышал о двух решениях:

1) Используйте пакет savedInstanceState для изменения ориентации, но также включите SharedPrefs, когда приложение должно быть полностью отключено.

Это кажется невероятно контрпродуктивным, поскольку он использует 2 разных полностью метода, чтобы делать в основном одно и то же. Кроме того, поскольку my savedInstanceState Bundle использует объекты Parcelable, я должен был бы дать каждому из этих объектов другой метод, чтобы они могли быть записаны в SharedPrefs. Существенно LOTS дублированного и трудно управляемого кода.

2) Сериализовать пакет savedInstanceState Bundle и записать его непосредственно в файл.

Я открыт для этого, но на самом деле я не знаю, как это сделать. Тем не менее, я все еще держу надежду на то, что может быть лучшее решение, поскольку я слышал, что сериализация в Android "комично/необычно медленно".

Я был бы чрезвычайно благодарен, если бы кто-то мог предоставить мне решение этого.

Ответ 1

Теперь я придумал свое решение этой проблемы, которое является полуавтоматическим средством сохранения пакетов в SharedPreferences. Я говорю полуавтоматически, потому что, хотя сохранение пакета требует только одного метода, получение данных снова и превращение его обратно в 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();
}

Обратите внимание, что у меня есть только письменные случаи для типов, которые мне нужны, но это должно быть легко адаптируемо, если у вас есть Bundles, которые также включают другие типы.

Этот метод будет рекурсивно сохранять другие объекты Bundle, хранящиеся внутри данного Bundle. Тем не менее, он не будет работать для объектов Parcelable, поэтому мне пришлось изменить объекты Parcelable, чтобы они вместо этого хранили себя в Bundle. Поскольку Parcels и Bundles довольно похожи, это было не слишком сложно. Я думаю, что 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 используют тот же метод.

Ответ 3

Я продлил ответ от Дэна, чтобы функция автоматически воссоздавала Связки, и сделала имена менее вероятными для столкновения.

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