L'accesso a SharedPreferences deve essere eseguito dal thread dell'interfaccia utente?


113

Con il rilascio di Gingerbread, ho sperimentato alcune delle nuove API, una delle quali è StrictMode .

Ho notato che uno degli avvertimenti è per getSharedPreferences().

Questo è l'avvertimento:

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

e viene fornito per una getSharedPreferences()chiamata effettuata sul thread dell'interfaccia utente.

L' SharedPreferencesaccesso e le modifiche dovrebbero essere davvero effettuati dal thread dell'interfaccia utente?


Ho sempre effettuato le mie operazioni di preferenza sul thread dell'interfaccia utente. Anche se immagino abbia senso visto che è un'operazione IO
Falmarri

Risposte:


184

Sono contento che tu ci stia già giocando!

Alcune cose da notare: (in forma di pallino pigro)

  • se questo è il peggiore dei tuoi problemi, probabilmente la tua app è in una buona posizione. :) Le scritture sono generalmente più lente delle letture, quindi assicurati di utilizzare SharedPreferenced $ Editor.apply () invece di commit (). apply () è nuovo in GB e async (ma sempre sicuro, attento alle transizioni del ciclo di vita). Puoi usare la reflection per chiamare in modo condizionale apply () su GB + e commit () su Froyo o sotto. Farò un post sul blog con un codice di esempio su come eseguire questa operazione.

Per quanto riguarda il caricamento, però ...

  • una volta caricate, le SharedPreferences sono singole e memorizzate nella cache a livello di processo. quindi vuoi caricarlo il prima possibile in modo da averlo in memoria prima di averne bisogno. (supponendo che sia piccolo, come dovrebbe essere se stai usando SharedPreferences, un semplice file XML ...) Non vuoi criticare in futuro qualche utente fa clic su un pulsante.

  • ma ogni volta che chiami context.getSharedPreferences (...), il file XML di supporto viene statuito per vedere se è cambiato, quindi ti consigliamo di evitare comunque quelle statistiche durante gli eventi dell'interfaccia utente. Una statistica dovrebbe normalmente essere veloce (e spesso memorizzata nella cache), ma yaffs non ha molto in termini di concorrenza (e molti dispositivi Android funzionano su yaff ... Droid, Nexus One, ecc.) Quindi se eviti il ​​disco , eviti di rimanere bloccato dietro ad altre operazioni del disco in volo o in sospeso.

  • quindi probabilmente vorrai caricare le SharedPreferences durante onCreate () e riutilizzare la stessa istanza, evitando lo stat.

  • ma se non hai comunque bisogno delle tue preferenze durante onCreate (), quel tempo di caricamento blocca inutilmente l'avvio della tua app, quindi è generalmente meglio avere qualcosa come una sottoclasse FutureTask <SharedPreferences> che dà il via a un nuovo thread a .set () il valore delle sottoclassi FutureTask. Quindi cerca il membro di FutureTask <SharedPreferences> ogni volta che ne hai bisogno e .get (). Ho intenzione di renderlo gratuito dietro le quinte in Honeycomb, in modo trasparente. Proverò a rilasciare un codice di esempio che mostri le migliori pratiche in quest'area.

Controlla il blog degli sviluppatori Android per i prossimi post su argomenti correlati a StrictMode nelle prossime settimane.


Wow, non mi aspettavo di ottenere una risposta così chiara direttamente dalla fonte! Grazie mille
cottonBallPaws

9
A beneficio dei nuovi lettori di questo meraviglioso post, trovi di seguito il link al post del blog menzionato sopra da @Brad Fitzpatrick: post del blog dello sviluppatore Android in modalità rigorosa di Brad . Il post contiene anche un collegamento al codice di esempio per l'utilizzo di apply (dal pan di zenzero in poi) o commit (froyo) in base alla versione Android per l'archiviazione delle preferenze condivise: [conditionally use apply or commit] ( code.google.com/p/zippy-android / source / browse / trunk / examples /… )
tony m

4
È ancora rilevante su ICS \ JB?
ekatz

5

L'accesso alle preferenze condivise può richiedere molto tempo perché vengono lette dalla memoria flash. Leggi molto? Forse potresti usare un formato diverso, ad esempio un database SQLite.

Ma non aggiustare tutto ciò che trovi usando StrictMode. O per citare la documentazione:

Ma non sentirti obbligato a riparare tutto ciò che trova StrictMode. In particolare, molti casi di accesso al disco sono spesso necessari durante il normale ciclo di vita dell'attività. Usa StrictMode per trovare cose che hai fatto per sbaglio. Tuttavia, le richieste di rete sul thread dell'interfaccia utente sono quasi sempre un problema.


6
Ma SQLite non è anche un file che deve essere letto dalla memoria flash, ma uno più grande e più complicato rispetto a un file delle preferenze. Ho ipotizzato che, per la quantità di dati associati alle preferenze, un file delle preferenze sarà molto più veloce di un database SQLite.
Tom

È corretto. Come già accennato da Brad, questo quasi sempre non è un problema - e afferma anche che è una buona idea caricare le SharedPreferences una volta (forse anche in un thread utilizzando un FutureTask) e tenerlo per ogni possibile accesso alla singola istanza.
mreichelt

5

Una sottigliezza sulla risposta di Brad: anche se carichi le SharedPreferences in onCreate (), probabilmente dovresti comunque leggere i valori sul thread in background perché getString () ecc. Si blocca fino a leggere la preferenza del file condiviso in finish (su un thread in background):

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

Anche edit () si blocca allo stesso modo, sebbene apply () sembri essere sicuro sul thread in primo piano.

(A proposito, mi dispiace metterlo qui. Avrei messo questo come un commento alla risposta di Brad, ma mi sono appena iscritto e non ho abbastanza reputazione per farlo.)


1

So che questa è una vecchia domanda, ma voglio condividere il mio approccio. Ho avuto lunghi tempi di lettura e ho utilizzato una combinazione di preferenze condivise e la classe dell'applicazione 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 (attività che viene chiamata per prima nella tua applicazione):

LocalPreference.getLocalPreferences(this);

Passaggi spiegati:

  1. L'attività principale chiama getLocalPreferences (this) -> questo leggerà le tue preferenze, imposterà l'oggetto filtro nella tua classe dell'applicazione e lo restituirà.
  2. Quando chiami di nuovo la funzione getLocalPreferences () da qualche altra parte nell'applicazione, prima controlla se non è disponibile nella classe dell'applicazione, che è molto più veloce.

NOTA: controlla SEMPRE se una variabile a livello di applicazione è diversa da NULL, motivo -> http://www.developerphil.com/dont-store-data-in-the-application-object/

L'oggetto dell'applicazione non rimarrà in memoria per sempre, verrà ucciso. Contrariamente alla credenza popolare, l'app non verrà riavviata da zero. Android creerà un nuovo oggetto Application e avvierà l'attività in cui si trovava l'utente prima per dare l'illusione che l'applicazione non sia mai stata uccisa in primo luogo.

Se non avessi verificato su null, consentirei il lancio di un nullpointer durante la chiamata ad esempio getMaxDistance () sull'oggetto filtro (se l'oggetto dell'applicazione è stato spostato dalla memoria da Android)


0

La classe SharedPreferences esegue alcune operazioni di lettura e scrittura all'interno di file XML su disco, quindi, proprio come qualsiasi altra operazione di I / O, potrebbe bloccarsi. La quantità di dati attualmente archiviati in SharedPreferences influisce sul tempo e sulle risorse consumati dalle chiamate API. Per quantità minime di dati è questione di pochi millisecondi (a volte anche meno di un millisecondo) per ottenere / inserire dati. Ma dal punto di vista di un esperto potrebbe essere importante migliorare le prestazioni facendo le chiamate API in background. Per una SharedPreferences asincrona suggerisco di controllare la libreria Datum .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.