L'accès à SharedPreferences doit-il être effectué à partir du thread d'interface utilisateur?

113

Avec la sortie de Gingerbread, j'ai expérimenté certaines des nouvelles API, l'une d'entre elles étant StrictMode .

J'ai remarqué que l'un des avertissements est pour getSharedPreferences().

Voici l'avertissement:

StrictMode policy violation; ~duration=1949 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=23 violation=2

et il est donné pour un getSharedPreferences()appel effectué sur le thread d'interface utilisateur.

L' SharedPreferencesaccès et les modifications doivent-ils vraiment être effectués hors du thread de l'interface utilisateur?

cotonBallPaws
la source
J'ai toujours effectué mes opérations de préférence sur le thread de l'interface utilisateur. Bien que je suppose que cela a du sens puisqu'il s'agit d'une opération IO
Falmarri

Réponses:

184

Je suis content que vous jouiez déjà avec!

Quelques points à noter: (sous forme de puce paresseuse)

  • si c'est le pire de vos problèmes, votre application est probablement au bon endroit. :) Les écritures sont généralement plus lentes que les lectures, alors assurez-vous que vous utilisez SharedPreferenced $ Editor.apply () au lieu de commit (). apply () est nouveau en GB et asynchrone (mais toujours sûr, attention aux transitions du cycle de vie). Vous pouvez utiliser la réflexion pour appeler conditionnellement apply () sur GB + et commit () sur Froyo ou ci-dessous. Je vais faire un article de blog avec un exemple de code expliquant comment procéder.

En ce qui concerne le chargement, cependant ...

  • une fois chargées, les SharedPreferences sont des singletons et mis en cache à l'échelle du processus. vous voulez donc le charger le plus tôt possible afin de l'avoir en mémoire avant d'en avoir besoin. (en supposant qu'il soit petit, comme il se doit si vous utilisez SharedPreferences, un simple fichier XML ...) Vous ne voulez pas le critiquer à l'avenir, un utilisateur clique sur un bouton.

  • mais chaque fois que vous appelez context.getSharedPreferences (...), le fichier XML de sauvegarde est statué pour voir s'il a changé, donc vous voudrez quand même éviter ces statistiques pendant les événements de l'interface utilisateur. Une statistique devrait normalement être rapide (et souvent mise en cache), mais les yaffs n'ont pas beaucoup de concurrence (et beaucoup d'appareils Android fonctionnent sur des yaffs ... Droid, Nexus One, etc.) donc si vous évitez le disque , vous évitez de rester coincé derrière d'autres opérations de disque en cours ou en attente.

  • vous voudrez donc probablement charger les SharedPreferences pendant votre onCreate () et réutiliser la même instance, en évitant la statistique.

  • mais si vous n'avez de toute façon pas besoin de vos préférences pendant onCreate (), ce temps de chargement stoppe inutilement le démarrage de votre application, il est donc généralement préférable d'avoir quelque chose comme une sous-classe FutureTask <SharedPreferences> qui lance un nouveau thread vers .set () la valeur des sous-classes FutureTask. Ensuite, recherchez simplement le membre de votre FutureTask <SharedPreferences> chaque fois que vous en avez besoin et .get (). Je prévois de rendre cela gratuit dans les coulisses de Honeycomb, de manière transparente. Je vais essayer de publier un exemple de code qui montre les meilleures pratiques dans ce domaine.

Consultez le blog des développeurs Android pour les articles à venir sur des sujets liés à StrictMode dans la ou les semaines à venir.

Brad Fitzpatrick
la source
Wow, je ne m'attendais pas à obtenir une réponse aussi claire directement de la source! Merci beaucoup!
cottonBallPaws
9
Pour le bénéfice des nouveaux lecteurs de ce merveilleux article, retrouvez ci-dessous le lien vers l'article de blog mentionné ci-dessus par @Brad Fitzpatrick: article de blog du développeur android sur le mode strict par Brad . Le message contient également un lien vers un exemple de code pour utiliser apply (à partir de pain d'épice) ou commit (froyo) basé sur la version Android pour stocker les préférences partagées: [conditionally use apply or commit] ( code.google.com/p/zippy-android / source / parcourir / trunk / examples /… )
tony m
4
Est-ce toujours pertinent sur ICS \ JB?
ekatz
5

L'accès aux préférences partagées peut prendre un certain temps car elles sont lues à partir du stockage flash. Lis-tu beaucoup? Vous pourriez peut-être utiliser un format différent, par exemple une base de données SQLite.

Mais ne corrigez pas tout ce que vous trouvez en utilisant StrictMode. Ou pour citer la documentation:

Mais ne vous sentez pas obligé de réparer tout ce que StrictMode trouve. En particulier, de nombreux cas d'accès au disque sont souvent nécessaires au cours du cycle de vie normal de l'activité. Utilisez StrictMode pour trouver des choses que vous avez faites par accident. Les requêtes réseau sur le thread d'interface utilisateur sont cependant presque toujours un problème.

Michelt
la source
6
Mais le SQLite n'est-il pas aussi un fichier qui doit être lu à partir du stockage flash - mais un fichier plus gros et plus compliqué qu'un fichier de préférences. J'ai supposé que, pour la quantité de données associées aux préférences, un fichier de préférences serait beaucoup plus rapide qu'une base de données SQLite.
Tom
C'est correct. Comme Brad l'a déjà mentionné, cela ne pose presque toujours aucun problème - et il mentionne également que c'est une bonne idée de charger les SharedPreferences une fois (peut-être même dans un thread en utilisant une FutureTask) et de les conserver pour tout accès possible à l'instance unique.
mreichelt
5

Une subtilité à propos de la réponse de Brad: même si vous chargez les SharedPreferences dans onCreate (), vous devriez probablement toujours lire les valeurs sur le thread d'arrière-plan car getString () etc. bloquent jusqu'à ce que la lecture de la préférence de fichier partagé se termine (sur un thread d'arrière-plan):

public String getString(String key, String defValue) {
    synchronized (this) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

edit () bloque également de la même manière, bien que apply () semble être sûr sur le thread de premier plan.

(BTW désolé de mettre cela ici. J'aurais mis cela en commentaire à la réponse de Brad, mais je viens de me joindre et je n'ai pas assez de réputation pour le faire.)

Tom O'Neill
la source
1

Je sais que c'est une vieille question mais je veux partager mon approche. J'ai eu de longs temps de lecture et j'ai utilisé une combinaison de préférences partagées et de la classe d'application globale:

ApplicationClass:

public class ApplicationClass extends Application {

    private LocalPreference.Filter filter;

    public LocalPreference.Filter getFilter() {
       return filter;
    }

    public void setFilter(LocalPreference.Filter filter) {
       this.filter = filter;
    }
}

LocalPreference:

public class LocalPreference {

    public static void saveLocalPreferences(Activity activity, int maxDistance, int minAge,
                                            int maxAge, boolean showMale, boolean showFemale) {

        Filter filter = new Filter();
        filter.setMaxDistance(maxDistance);
        filter.setMinAge(minAge);
        filter.setMaxAge(maxAge);
        filter.setShowMale(showMale);
        filter.setShowFemale(showFemale);

        BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
        babysitApplication.setFilter(filter);

        SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
        securePreferences.edit().putInt(Preference.FILER_MAX_DISTANCE.toString(), maxDistance).apply();
        securePreferences.edit().putInt(Preference.FILER_MIN_AGE.toString(), minAge).apply();
        securePreferences.edit().putInt(Preference.FILER_MAX_AGE.toString(), maxAge).apply();
        securePreferences.edit().putBoolean(Preference.FILER_SHOW_MALE.toString(), showMale).apply();
        securePreferences.edit().putBoolean(Preference.FILER_SHOW_FEMALE.toString(), showFemale).apply();
    }

    public static Filter getLocalPreferences(Activity activity) {

        BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
        Filter applicationFilter = babysitApplication.getFilter();

        if (applicationFilter != null) {
            return applicationFilter;
        } else {
            Filter filter = new Filter();
            SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
            filter.setMaxDistance(securePreferences.getInt(Preference.FILER_MAX_DISTANCE.toString(), 20));
            filter.setMinAge(securePreferences.getInt(Preference.FILER_MIN_AGE.toString(), 15));
            filter.setMaxAge(securePreferences.getInt(Preference.FILER_MAX_AGE.toString(), 50));
            filter.setShowMale(securePreferences.getBoolean(Preference.FILER_SHOW_MALE.toString(), true));
            filter.setShowFemale(securePreferences.getBoolean(Preference.FILER_SHOW_FEMALE.toString(), true));
            babysitApplication.setFilter(filter);
            return filter;
        }
    }

    public static class Filter {
        private int maxDistance;
        private int minAge;
        private int maxAge;
        private boolean showMale;
        private boolean showFemale;

        public int getMaxDistance() {
            return maxDistance;
        }

        public void setMaxDistance(int maxDistance) {
            this.maxDistance = maxDistance;
        }

        public int getMinAge() {
            return minAge;
        }

        public void setMinAge(int minAge) {
            this.minAge = minAge;
        }

        public int getMaxAge() {
            return maxAge;
        }

        public void setMaxAge(int maxAge) {
            this.maxAge = maxAge;
        }

        public boolean isShowMale() {
            return showMale;
        }

        public void setShowMale(boolean showMale) {
            this.showMale = showMale;
        }

        public boolean isShowFemale() {
            return showFemale;
        }

        public void setShowFemale(boolean showFemale) {
            this.showFemale = showFemale;
        }
    }

}

MainActivity (activité qui est appelée en premier dans votre application):

LocalPreference.getLocalPreferences(this);

Étapes expliquées:

  1. L'activité principale appelle getLocalPreferences (this) -> cela lira vos préférences, définira l'objet de filtre dans votre classe d'application et le retournera.
  2. Lorsque vous appelez à nouveau la fonction getLocalPreferences () ailleurs dans l'application, elle vérifie d'abord si elle n'est pas disponible dans la classe d'application, ce qui est beaucoup plus rapide.

REMARQUE: vérifiez TOUJOURS si une variable à l'échelle de l'application est différente de NULL, raison -> http://www.developerphil.com/dont-store-data-in-the-application-object/

L'objet d'application ne restera pas indéfiniment en mémoire, il sera tué. Contrairement à la croyance populaire, l'application ne sera pas redémarrée à partir de zéro. Android créera un nouvel objet Application et démarrera l'activité là où l'utilisateur se trouvait auparavant pour donner l'illusion que l'application n'a jamais été tuée en premier lieu.

Si je ne vérifiais pas la valeur null, j'autoriserais le lancement d'un pointeur nul lors de l'appel, par exemple, de getMaxDistance () sur l'objet filtre (si l'objet d'application a été glissé de la mémoire par Android)

Jdruwe
la source
0

La classe SharedPreferences effectue des lectures et écritures dans des fichiers XML sur le disque, donc comme toute autre opération IO, elle peut être bloquante. La quantité de données actuellement stockées dans SharedPreferences affecte le temps et les ressources consommés par les appels d'API. Pour des quantités minimales de données, il suffit de quelques millisecondes (parfois même moins d'une milliseconde) pour obtenir / placer des données. Mais du point de vue d'un expert, il pourrait être important d'améliorer les performances en effectuant les appels d'API en arrière-plan. Pour un SharedPreferences asynchrone, je suggère de consulter la bibliothèque Datum .

navid
la source