Czy dostęp do SharedPreferences powinien odbywać się z poziomu wątku interfejsu użytkownika?

113

Wraz z wydaniem Gingerbread eksperymentowałem z niektórymi nowymi API, z których jeden to StrictMode .

Zauważyłem, że jedno z ostrzeżeń dotyczy getSharedPreferences().

To jest ostrzeżenie:

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

i jest podawany dla getSharedPreferences()wywołania w wątku interfejsu użytkownika.

Czy SharedPreferencesdostęp i zmiany naprawdę powinny być dokonywane poza wątkiem interfejsu użytkownika?

cottonBallPaws
źródło
Zawsze wykonywałem operacje preferencji w wątku interfejsu użytkownika. Chociaż wydaje mi się, że ma to sens, ponieważ jest to operacja IO
Falmarri

Odpowiedzi:

184

Cieszę się, że już się tym bawisz!

Kilka uwag: (w leniwej formie punktora)

  • jeśli to najgorszy z Twoich problemów, prawdopodobnie Twoja aplikacja jest w dobrym miejscu. :) Zapisy są jednak generalnie wolniejsze niż odczyty, więc upewnij się, że używasz SharedPreferenced $ Editor.apply () zamiast commit (). Apply () jest nowy w GB i asynchroniczny (ale zawsze bezpieczny, uważaj na przejścia w cyklu życia). Możesz użyć refleksji, aby warunkowo wywołać Apply () na GB + i commit () na Froyo lub poniżej. Opublikuję post na blogu z przykładowym kodem pokazującym, jak to zrobić.

Ale jeśli chodzi o ładowanie ...

  • po załadowaniu SharedPreferences są pojedyncze i buforowane w całym procesie. więc chcesz załadować go jak najwcześniej, aby mieć go w pamięci, zanim będzie potrzebny. (zakładając, że jest mały, tak jak powinien być, jeśli używasz SharedPreferences, prostego pliku XML ...) Nie chcesz w przyszłości nic zarzucić, gdy jakiś użytkownik kliknie przycisk.

  • ale za każdym razem, gdy wywołujesz context.getSharedPreferences (...), zapasowy plik XML jest statystycznie sprawdzany, czy został zmieniony, więc i tak będziesz chciał uniknąć tych statystyk podczas zdarzeń interfejsu użytkownika. Statystyka powinna być normalnie szybka (i często zapisywana w pamięci podręcznej), ale yaffy nie mają zbyt wiele na drodze współbieżności (a wiele urządzeń z Androidem działa na yaffach ... Droid, Nexus One itp.), Więc jeśli unikniesz dysku , unikniesz utknięcia za innymi operacjami w locie lub oczekującymi na dysku.

  • więc prawdopodobnie będziesz chciał załadować SharedPreferences podczas onCreate () i ponownie użyć tej samej instancji, unikając statystyki.

  • ale jeśli i tak nie potrzebujesz swoich preferencji podczas onCreate (), ten czas ładowania niepotrzebnie opóźnia uruchomienie aplikacji, więc generalnie lepiej jest mieć coś w rodzaju podklasy FutureTask <SharedPreferences>, która uruchamia nowy wątek do .set. () wartość podklas FutureTask. Następnie po prostu wyszukaj członka swojego FutureTask <SharedPreferences>, kiedy tylko tego potrzebujesz i .get () go. Planuję udostępnić to za kulisami w Honeycomb, w sposób przejrzysty. Spróbuję opublikować przykładowy kod, który pokazuje najlepsze praktyki w tej dziedzinie.

Sprawdź blog programistów Androida, aby zobaczyć nadchodzące posty na tematy związane ze StrictMode w nadchodzących tygodniach.

Brad Fitzpatrick
źródło
Wow, nie spodziewałem się, że otrzymam tak jasną odpowiedź prosto od źródła! Dzięki wielkie!
cottonBallPaws
9
Z korzyścią dla nowych czytelników tego wspaniałego posta, poniżej znajduje się link do wpisu na blogu wspomnianego powyżej przez @Brad Fitzpatrick: post na blogu programisty Androida w trybie ścisłym autorstwa Brada . W poście znajduje się również link do przykładowego kodu do używania Apply (od piernika) lub commit (froyo) w oparciu o wersję Androida do przechowywania wspólnych preferencji: [warunkowo zastosuj lub zatwierdź] ( code.google.com/p/zippy-android / źródło / przeglądaj / bagażnik / przykłady /… )
tony m
4
Czy jest to nadal aktualne w ICS \ JB?
ekatz
5

Dostęp do wspólnych preferencji może zająć trochę czasu, ponieważ są one odczytywane z pamięci flash. Czy dużo czytasz? Może mógłbyś wtedy użyć innego formatu, np. Bazy danych SQLite.

Ale nie naprawiaj wszystkiego, co znajdziesz, używając StrictMode. Lub zacytować dokumentację:

Ale nie czuj się zmuszony do naprawiania wszystkiego, co znajdzie StrictMode. W szczególności wiele przypadków dostępu do dysku jest często koniecznych podczas normalnego cyklu życia. Użyj StrictMode, aby znaleźć rzeczy, które zrobiłeś przez przypadek. Jednak żądania sieciowe w wątku interfejsu użytkownika są prawie zawsze problemem.

mreichelt
źródło
6
Ale czy SQLite nie jest również plikiem, który trzeba odczytać z pamięci flash - ale większy i bardziej skomplikowany w porównaniu do pliku preferencji. Zakładałem, że ze względu na ilość danych związanych z preferencjami plik preferencji będzie znacznie szybszy niż baza danych SQLite.
Tom
To jest poprawne. Jak już wspomniał Brad, prawie zawsze nie stanowi to problemu - i wspomina również, że dobrym pomysłem jest jednorazowe załadowanie SharedPreferences (może nawet w wątku przy użyciu FutureTask) i zatrzymanie go, aby uzyskać dostęp do pojedynczego wystąpienia.
mreichelt
5

Jedna subtelność w odpowiedzi Brada: nawet jeśli załadujesz SharedPreferences w onCreate (), prawdopodobnie nadal powinieneś czytać wartości w wątku w tle, ponieważ getString () itp. Blokuje się do momentu odczytania preferencji udostępnionego pliku w zakończeniach (w wątku w tle):

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

edit () również blokuje w ten sam sposób, chociaż apply () wydaje się być bezpieczny w wątku pierwszego planu.

(Swoją drogą, przepraszam, że to tutaj zapisuję. Umieściłbym to jako komentarz do odpowiedzi Brada, ale właśnie dołączyłem i nie mam wystarczającej reputacji, aby to zrobić.)

Tom O'Neill
źródło
1

Wiem, że to stare pytanie, ale chcę się podzielić moim podejściem. Miałem długi czas czytania i korzystałem z kombinacji wspólnych preferencji i globalnej klasy aplikacji:

Klasa aplikacji:

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 (aktywność, która jest wywoływana jako pierwsza w aplikacji):

LocalPreference.getLocalPreferences(this);

Kroki wyjaśnione:

  1. Główne działanie wywołuje getLocalPreferences (this) -> to odczyta twoje preferencje, ustawi obiekt filtru w twojej klasie aplikacji i zwróci go.
  2. Kiedy ponownie wywołujesz funkcję getLocalPreferences () w innym miejscu aplikacji, najpierw sprawdza, czy nie jest ona dostępna w klasie aplikacji, co jest o wiele szybsze.

UWAGA: ZAWSZE sprawdzaj, czy zmienna dotycząca całej aplikacji różni się od NULL, powód -> http://www.developerphil.com/dont-store-data-in-the-application-object/

Obiekt aplikacji nie pozostanie w pamięci na zawsze, zostanie zabity. Wbrew powszechnemu przekonaniu aplikacja nie zostanie ponownie uruchomiona od zera. Android utworzy nowy obiekt Application i rozpocznie działanie, na którym wcześniej znajdował się użytkownik, dając złudzenie, że aplikacja nigdy nie została zabita.

Gdybym nie sprawdzał na null, pozwoliłbym na wyrzucenie nullpointer podczas wywoływania na przykład getMaxDistance () na obiekcie filtru (jeśli obiekt aplikacji został usunięty z pamięci przez Androida)

Jdruwe
źródło
0

Klasa SharedPreferences wykonuje pewne odczyty i zapisy w plikach XML na dysku, więc podobnie jak każda inna operacja we / wy może blokować. Ilość danych aktualnie przechowywanych w SharedPreferences wpływa na czas i zasoby zużywane przez wywołania interfejsu API. W przypadku minimalnych ilości danych pobranie / umieszczenie danych zajmuje kilka milisekund (czasami nawet mniej niż milisekundę). Jednak z punktu widzenia eksperta może być ważne, aby poprawić wydajność, wykonując wywołania API w tle. W przypadku asynchronicznych SharedPreferences sugeruję sprawdzenie biblioteki Datum .

navid
źródło