Sollte der Zugriff auf SharedPreferences über den UI-Thread erfolgen?

112

Mit der Veröffentlichung von Gingerbread habe ich mit einigen der neuen APIs experimentiert, darunter StrictMode .

Mir ist aufgefallen, dass eine der Warnungen für ist getSharedPreferences().

Dies ist die Warnung:

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

und es wird für einen getSharedPreferences()Aufruf gegeben, der auf dem UI-Thread getätigt wird.

Sollten SharedPreferencesZugriff und Änderungen wirklich über den UI-Thread vorgenommen werden?

CottonBallPaws
quelle
Ich habe immer meine Präferenzoperationen für den UI-Thread vorgenommen. Obwohl ich denke, dass es sinnvoll ist, da es sich um eine
E / A-

Antworten:

184

Ich bin froh, dass du schon damit spielst!

Einige Dinge zu beachten: (in fauler Kugelform)

  • Wenn dies das schlimmste Ihrer Probleme ist, ist Ihre App wahrscheinlich an einem guten Ort. :) Schreibvorgänge sind im Allgemeinen langsamer als Lesevorgänge. Stellen Sie daher sicher, dass Sie SharedPreferenced $ Editor.apply () anstelle von commit () verwenden. apply () ist neu in GB und asynchron (aber immer sicher, vorsichtig bei Lebenszyklusübergängen). Sie können Reflection verwenden, um apply () unter GB + und commit () unter Froyo oder darunter bedingt aufzurufen. Ich werde einen Blogpost mit Beispielcode machen, wie das geht.

In Bezug auf das Laden, obwohl ...

  • Nach dem Laden sind SharedPreferences Singletons und werden prozessweit zwischengespeichert. Sie möchten es also so früh wie möglich laden, damit Sie es im Speicher haben, bevor Sie es benötigen. (Vorausgesetzt, es ist klein, wie es sein sollte, wenn Sie SharedPreferences verwenden, eine einfache XML-Datei ...) Sie möchten es in Zukunft nicht mehr bemängeln, wenn ein Benutzer auf eine Schaltfläche klickt.

  • Wenn Sie jedoch context.getSharedPreferences (...) aufrufen, wird in der XML-Sicherungsdatei angezeigt, ob sie geändert wurde. Daher sollten Sie diese Statistiken bei UI-Ereignissen trotzdem vermeiden. Eine Statistik sollte normalerweise schnell sein (und häufig zwischengespeichert werden), aber Yaffs haben nicht viel Parallelität (und viele Android-Geräte laufen auf Yaffs ... Droid, Nexus One usw.). Wenn Sie also die Festplatte meiden Vermeiden Sie es, hinter anderen laufenden oder ausstehenden Festplattenvorgängen hängen zu bleiben.

  • Daher möchten Sie wahrscheinlich die SharedPreferences während Ihres onCreate () laden und dieselbe Instanz erneut verwenden, um den Status zu vermeiden.

  • Wenn Sie Ihre Einstellungen während onCreate () ohnehin nicht benötigen, verzögert diese Ladezeit den Start Ihrer App unnötig. Daher ist es im Allgemeinen besser, eine FutureTask <SharedPreferences> -Unterklasse zu haben, die einen neuen Thread für .set startet () Der Wert der FutureTask-Unterklassen. Suchen Sie dann einfach nach dem Mitglied Ihres FutureTask <SharedPreferences>, wann immer Sie es benötigen, und .get (). Ich habe vor, dies hinter den Kulissen von Honeycomb transparent zu machen. Ich werde versuchen, einen Beispielcode zu veröffentlichen, der Best Practices in diesem Bereich zeigt.

Überprüfen Sie den Blog der Android-Entwickler auf bevorstehende Beiträge zu StrictMode-Themen in den kommenden Wochen.

Brad Fitzpatrick
quelle
Wow, ich hatte nicht erwartet, eine so klare Antwort direkt von der Quelle zu bekommen! Vielen Dank!
CottonBallPaws
9
Für neue Leser dieses wunderbaren Beitrags finden Sie unten den Link zu dem oben erwähnten Blog-Beitrag von @Brad Fitzpatrick: Blog-Beitrag des Android-Entwicklers zum strengen Modus von Brad . Der Beitrag enthält auch einen Link zum Beispielcode für die Verwendung von apply (ab Lebkuchen) oder commit (froyo) basierend auf der Android-Version zum Speichern gemeinsamer Einstellungen: [bedingt verwenden apply oder commit] ( code.google.com/p/zippy-android) / source / browse / trunk / examples /… )
Tony m
4
Ist dies in ICS \ JB noch relevant?
Ekatz
5

Der Zugriff auf die freigegebenen Einstellungen kann einige Zeit dauern, da sie aus dem Flash-Speicher gelesen werden. Liest du viel? Vielleicht könnten Sie dann ein anderes Format verwenden, z. B. eine SQLite-Datenbank.

Korrigieren Sie jedoch nicht alles, was Sie mit StrictMode finden. Oder um die Dokumentation zu zitieren:

Aber fühlen Sie sich nicht gezwungen, alles zu reparieren, was StrictMode findet. Insbesondere sind viele Fälle von Festplattenzugriff häufig während des normalen Aktivitätslebenszyklus erforderlich. Verwenden Sie StrictMode, um Dinge zu finden, die Sie versehentlich getan haben. Netzwerkanforderungen im UI-Thread sind jedoch fast immer ein Problem.

mreichelt
quelle
6
Aber ist die SQLite nicht auch eine Datei, die aus dem Flash-Speicher gelesen werden muss - sondern eine größere und kompliziertere im Vergleich zu einer Voreinstellungsdatei. Ich bin davon ausgegangen, dass eine Einstellungsdatei für die mit Einstellungen verknüpfte Datenmenge viel schneller ist als eine SQLite-Datenbank.
Tom
Das ist richtig. Wie Brad bereits erwähnt hat, ist dies fast immer kein Problem - und er erwähnt auch, dass es eine gute Idee ist, die SharedPreferences einmal zu laden (möglicherweise sogar in einem Thread mit einer FutureTask) und sie für einen möglichen Zugriff auf die einzelne Instanz zu halten.
mreichelt
5

Eine Subtilität zu Brads Antwort: Selbst wenn Sie die SharedPreferences in onCreate () laden, sollten Sie wahrscheinlich immer noch Werte im Hintergrundthread lesen, da getString () usw. blockieren, bis Sie die Einstellungen für gemeinsam genutzte Dateien in den Endspielen (in einem Hintergrundthread) lesen:

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

edit () blockiert ebenfalls auf die gleiche Weise, obwohl apply () im Vordergrund-Thread sicher zu sein scheint.

(Übrigens, es tut mir leid, das hier niederzulegen. Ich hätte dies als Kommentar zu Brads Antwort eingefügt, aber ich bin gerade beigetreten und habe nicht genug Ruf, um dies zu tun.)

Tom O'Neill
quelle
1

Ich weiß, dass dies eine alte Frage ist, aber ich möchte meinen Ansatz teilen. Ich hatte lange Lesezeiten und verwendete eine Kombination aus gemeinsamen Einstellungen und der globalen Anwendungsklasse:

Anwendungsklasse:

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 (Aktivität, die in Ihrer Anwendung zuerst aufgerufen wird):

LocalPreference.getLocalPreferences(this);

Schritte erklärt:

  1. Die Hauptaktivität ruft getLocalPreferences (this) -> auf. Dadurch werden Ihre Einstellungen gelesen, das Filterobjekt in Ihrer Anwendungsklasse festgelegt und zurückgegeben.
  2. Wenn Sie die Funktion getLocalPreferences () an einer anderen Stelle in der Anwendung erneut aufrufen, wird zunächst geprüft, ob sie in der viel schnelleren Anwendungsklasse nicht verfügbar ist.

HINWEIS: Überprüfen Sie IMMER, ob sich eine anwendungsweite Variable von NULL unterscheidet. Grund -> http://www.developerphil.com/dont-store-data-in-the-application-object/

Das Anwendungsobjekt bleibt nicht für immer im Speicher, es wird getötet. Entgegen der landläufigen Meinung wird die App nicht von Grund auf neu gestartet. Android erstellt ein neues Anwendungsobjekt und startet die Aktivität, bei der der Benutzer zuvor war, um die Illusion zu vermitteln, dass die Anwendung überhaupt nicht getötet wurde.

Wenn ich null nicht aktiviert hätte, würde ich zulassen, dass ein Nullzeiger ausgelöst wird, wenn beispielsweise getMaxDistance () für das Filterobjekt aufgerufen wird (wenn das Anwendungsobjekt von Android aus dem Speicher gewischt wurde).

Jdruwe
quelle