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

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

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

Я слышал о 2 решениях до сих пор:

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

Это кажется невероятно контрпродуктивным, поскольку использует 2 совершенно разных метода, чтобы делать одно и то же. Кроме того, поскольку мой пакет saveInstanceState Bundle использует объекты Parcelable, мне пришлось бы предоставить каждому из этих объектов другой метод, позволяющий записывать их в SharedPrefs. По сути, МНОГО дублированного и сложно управляемого кода.

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

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

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


person Dan    schedule 01.12.2012    source источник
comment
Чтобы сериализовать, просто найдите класс сериализации, его не должно быть очень сложно найти. Я не заметил никаких мучительных задержек при использовании   -  person mango    schedule 01.12.2012
comment
Единственная информация, которую я могу найти, говорит мне, что мне нужно реализовать Serializable, но Bundle не реализует этот интерфейс.   -  person Dan    schedule 02.12.2012
comment
Я рекомендую библиотеку github.com/iamironz/binaryprefs, она позволяет сохранять данные, как стандартная java, через реализацию Persistable интерфейса (Внешний интерфейс в jdk)   -  person denis.sugakov    schedule 31.10.2017


Ответы (3)


Забавно, на этой неделе в выпуске 47 Android Weekly появилась эта библиотека: сложные настройки Android.

Это должно подойти для вас.

person Snicolas    schedule 01.12.2012
comment
Это казалось таким многообещающим, но, увы, я ничего не могу заставить. Я пробовал с различными объектами Bundle, включая пустые, и некоторыми более простыми объектами, такими как Points, но все равно не повезло. Он либо жалуется на циклическую ссылку при сохранении, либо объект, хранящийся в ключе ___, является экземпляром другой игры при загрузке. Это сводит меня с ума... - person Dan; 01.12.2012
comment
Пожалуйста, опубликуйте это как отдельный вопрос. Мне было бы интересно, если бы вы добавили комментарий к моему адресу, чтобы я мог следить за ним. - person Snicolas; 01.12.2012
comment
На самом деле это просто сохранение всего как JSon с использованием Gson... Во всяком случае, я чувствую, что ваши данные могут быть внутренними классами чего-то. Это даст вам циклы очень легко. Являются ли ваши POJO отдельными классами? - person Snicolas; 01.12.2012
comment
Вот новый вопрос: stackoverflow.com/questions /13665389/ Кстати, спасибо за помощь. Кроме того, там, где я написал экземпляр другой игры в своем предыдущем комментарии, это должен был быть экземпляр другого класса. - person Dan; 02.12.2012

Теперь я придумал собственное решение этой проблемы, которое представляет собой полуавтоматическое средство сохранения пакетов в SharedPreferences. Я говорю полуавтоматический, потому что, хотя для сохранения пакета требуется только один метод, повторное извлечение данных и преобразование их обратно в пакет требует некоторой работы.

Вот код для сохранения 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. Однако это не будет работать для объектов 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 используют одну и ту же технику.

person Community    schedule 03.12.2012
comment
как мы можем получитьBundle в этой ситуации? Спасибо - person Nam Vu; 24.05.2013
comment
Я не уверен, что именно вы имеете в виду... После того, как пакет был сохранен в SharedPrefs, его можно получить, как и любой другой пакет. - person Dan; 25.05.2013
comment
какие ключевые и о здесь. Форма, где вы передаете эти аргументы - person ekjyot; 24.04.2014
comment
Не могли бы вы также рассказать, как получить пакет из общих настроек? - person ekjyot; 24.04.2014
comment
Извиняюсь!! Я потерял исходный код, но отредактировал сообщение, исходя из своих предположений о том, как оно должно выглядеть. Чтобы получить Bundle из SharedPreferences, вам нужно вручную заново создать ключи, как описано в нижней части моего поста, а затем использовать эти ключи для получения того, что вы хотите. Я начинаю думать, что сохранение Bundle в SharedPreferences не является разумной идеей, в конце концов :) - person Dan; 24.04.2014

Я расширил ответ Дэна с помощью функции автоматического воссоздания пакетов и сделал менее вероятным столкновение имен.

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;
}
person Neromancer    schedule 29.01.2015
comment
commons.apache.org/proper/commons-lang/javadocs/api-2.6/, чтобы узнать, что делают функции StringUtils (не включены в Android) - person James Alvarez; 26.02.2015