web-dev-qa-db-fra.com

Enregistrer le bundle dans SharedPreferences

Je me suis donné beaucoup de mal pour que toutes les données de mon Android s'intègrent dans un bundle InstanceState enregistré. Il y a beaucoup de données en tout, y compris de nombreux objets parcellaires. Cela garantit que lorsque l'application est interrompue ou l'orientation change, aucune donnée n'est perdue par l'activité en cours de recréation.

Cependant, je viens de découvrir récemment qu'un paquet saveInstanceState n'est apparemment PAS approprié pour un stockage à long terme. Je cherche donc un moyen d'adapter ma méthode de sauvegarde existante pour qu'elle fonctionne comme une solution à long terme, afin que l'état du jeu puisse toujours être restauré.

J'ai entendu parler de 2 solutions jusqu'à présent:

1) Utilisez le bundle savedInstanceState pour les changements d'orientation, mais incorporez également SharedPrefs lorsque l'application doit être complètement arrêtée.

Cela semble incroyablement contre-productif, car il utilise complètement deux méthodes différentes pour faire essentiellement la même chose. De plus, étant donné que mon bundle InstanceState enregistré utilise des objets Parcelable, je devrais donner à chacun de ces objets une autre méthode pour leur permettre d'être écrits dans SharedPrefs. Essentiellement BEAUCOUP de code dupliqué et difficile à gérer.

2) Sérialisez le Bundle Instance enregistré et écrivez-le directement dans un fichier.

Je suis ouvert à cela, mais je ne sais pas vraiment comment procéder. Cependant, je garde l'espoir qu'il puisse y avoir une meilleure solution, car j'ai entendu que la sérialisation dans Android est "comiquement/inhabituellement lente".

Je serais extrêmement reconnaissant si quelqu'un pouvait me fournir une solution à ce problème.

23
Dan

J'ai maintenant trouvé ma propre solution à ce problème, qui est un moyen semi-automatique d'enregistrer des bundles dans SharedPreferences. Je dis semi-automatique car, bien que l'enregistrement du bundle ne nécessite qu'une seule méthode, récupérer à nouveau les données et les reconvertir en bundle demande un certain travail.

Voici le code pour enregistrer le 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();
}

Notez que je n'ai écrit que des cas pour les types dont j'avais besoin, mais cela devrait être facilement adaptable si vous avez des bundles qui incluent également d'autres types.

Cette méthode enregistre récursivement d'autres objets Bundle stockés dans le Bundle donné. Cependant, cela ne fonctionnera pas pour les objets Parcelable, j'ai donc dû modifier mes objets Parcelable pour qu'ils se stockent plutôt dans un Bundle. Étant donné que les colis et les paquets sont assez similaires, cela n'a pas été trop difficile. Je pense que les paquets peuvent également être légèrement plus lents que les colis, malheureusement.

J'ai ensuite écrit des constructeurs dans tous mes objets précédemment parcellaires pour leur permettre de se regrouper à nouveau à partir des données SharedPreferences stockées. Il est assez facile de reconstruire les clés des données dont vous avez besoin. Supposons que vous ayez la structure de données suivante:

Bundle b {
    KEY_X -> int x;
    KEY_Y -> Bundle y {
                 KEY_Z -> int z;
             }
}

Celles-ci seront enregistrées dans SharedPreferences comme suit:

KEY_X -> x
KEY_YKEY_Z -> z

Ce n'est peut-être pas la plus jolie méthode au monde, mais cela fonctionne, et cela me coûte beaucoup moins de code que l'alternative, car maintenant ma méthode onSaveInstanceState et mes méthodes onPause utilisent la même technique.

4
Dan

Drôle, cette semaine, le numéro 47 de Android Weekly a déclenché cette bibliothèque: préférences complexes Android .

Cela devrait vous convenir.

18
Snicolas

J'ai étendu la réponse de Dan avec une fonction pour recréer automatiquement les bundles et rendre les noms moins susceptibles de se heurter.

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