¿Se debe acceder a SharedPreferences fuera del hilo de la interfaz de usuario?

112

Con el lanzamiento de Gingerbread, he estado experimentando con algunas de las nuevas API, una de ellas StrictMode .

Noté que una de las advertencias es para getSharedPreferences().

Esta es la advertencia:

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

y se está dando para una getSharedPreferences()llamada que se realiza en el hilo de la interfaz de usuario.

¿Deberían realizarse SharedPreferencescambios y acceso desde el hilo de la interfaz de usuario?

cottonBallPaws
fuente
Siempre hice mis operaciones de preferencia en el hilo de la interfaz de usuario. Aunque supongo que tiene sentido ya que es una operación IO
Falmarri

Respuestas:

184

¡Me alegra que ya estés jugando con él!

Algunas cosas a tener en cuenta: (en forma de bala perezosa)

  • si este es el peor de sus problemas, su aplicación probablemente esté en un buen lugar. :) Las escrituras son generalmente más lentas que las lecturas, así que asegúrese de usar SharedPreferenced $ Editor.apply () en lugar de commit (). apply () es nuevo en GB y async (pero siempre seguro, cuidado con las transiciones del ciclo de vida). Puede usar la reflexión para llamar condicionalmente a apply () en GB + y commit () en Froyo o debajo. Haré una entrada de blog con un código de muestra de cómo hacer esto.

Sin embargo, con respecto a la carga ...

  • una vez cargados, SharedPreferences son singletons y se almacenan en caché en todo el proceso. por lo que desea cargarlo lo antes posible para tenerlo en la memoria antes de que lo necesite. (asumiendo que es pequeño, como debería ser si está usando SharedPreferences, un archivo XML simple ...) No querrá que se le critique en el futuro si algún usuario hace clic en un botón.

  • pero cada vez que llama a context.getSharedPreferences (...), el archivo XML de respaldo se estadifica para ver si ha cambiado, por lo que querrá evitar esas estadísticas durante los eventos de la interfaz de usuario de todos modos. Una estadística normalmente debería ser rápida (y a menudo almacenada en caché), pero yaffs no tiene mucha simultaneidad (y muchos dispositivos Android se ejecutan en yaffs ... Droid, Nexus One, etc.) así que si evitas el disco , evita quedarse atascado detrás de otras operaciones de disco en curso o pendientes.

  • por lo que probablemente querrá cargar SharedPreferences durante su onCreate () y reutilizar la misma instancia, evitando el stat.

  • pero si no necesita sus preferencias de todos modos durante onCreate (), ese tiempo de carga está paralizando innecesariamente el inicio de su aplicación, por lo que generalmente es mejor tener algo como una subclase FutureTask <SharedPreferences> que inicie un nuevo hilo en .set () el valor de las subclases FutureTask. Luego, busque el miembro de FutureTask <SharedPreferences> siempre que lo necesite y .get (). Planeo hacer esto gratis detrás de escena en Honeycomb, de forma transparente. Intentaré publicar un código de muestra que muestre las mejores prácticas en esta área.

Consulte el blog de desarrolladores de Android para ver las próximas publicaciones sobre temas relacionados con StrictMode en las próximas semanas.

Brad Fitzpatrick
fuente
¡Vaya, no esperaba obtener una respuesta tan clara directamente de la fuente! ¡Muchas gracias!
cottonBallPaws
9
Para beneficio de los nuevos lectores de esta maravillosa publicación, encuentre a continuación el enlace a la publicación de blog mencionada anteriormente por @Brad Fitzpatrick: publicación de blog del desarrollador de Android en modo estricto de Brad . La publicación también tiene un enlace al código de muestra para usar aplicar (de pan de jengibre en adelante) o confirmar (froyo) según la versión de Android para almacenar preferencias compartidas: [usar condicionalmente aplicar o confirmar] ( code.google.com/p/zippy-android / fuente / navegar / tronco / ejemplos /… )
tony m
4
¿Sigue siendo relevante en ICS \ JB?
ekatz
5

El acceso a las preferencias compartidas puede llevar bastante tiempo porque se leen desde el almacenamiento flash. ¿Lees mucho? Quizás pueda usar un formato diferente, por ejemplo, una base de datos SQLite.

Pero no arregle todo lo que encuentre usando StrictMode. O citar la documentación:

Pero no se sienta obligado a arreglar todo lo que encuentra StrictMode. En particular, muchos casos de acceso al disco suelen ser necesarios durante el ciclo de vida normal de la actividad. Utilice StrictMode para encontrar cosas que hizo por accidente. Sin embargo, las solicitudes de red en el hilo de la IU son casi siempre un problema.

mreichelt
fuente
6
Pero el SQLite no es también un archivo que debe leerse desde el almacenamiento flash, sino uno más grande y complicado en comparación con un archivo de preferencias. He estado asumiendo que, por la cantidad de datos asociados con las preferencias, un archivo de preferencias será mucho más rápido que una base de datos SQLite.
Tom
Eso es correcto. Como Brad ya mencionó, esto casi siempre no es un problema, y ​​también menciona que es una buena idea cargar SharedPreferences una vez (tal vez incluso en un Thread usando FutureTask) y mantenerlo para cualquier posible acceso a la instancia única.
mreichelt
5

Una sutileza sobre la respuesta de Brad: incluso si carga SharedPreferences en onCreate (), probablemente aún debería leer los valores en el hilo de fondo porque getString (), etc. se bloquea hasta que lea la preferencia de archivo compartido en termina (en un hilo de fondo):

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

edit () también bloquea de la misma manera, aunque apply () parece ser seguro en el hilo de primer plano.

(Por cierto, lamento dejar esto aquí. Lo habría puesto como un comentario a la respuesta de Brad, pero me acabo de unir y no tengo la reputación suficiente para hacerlo).

Tom O'Neill
fuente
1

Sé que esta es una pregunta antigua, pero quiero compartir mi enfoque. Tuve tiempos de lectura prolongados y usé una combinación de preferencias compartidas y la clase de aplicación global:

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 (actividad que se llama primero en su aplicación):

LocalPreference.getLocalPreferences(this);

Pasos explicados:

  1. La actividad principal llama a getLocalPreferences (this) -> this leerá sus preferencias, establecerá el objeto de filtro en su clase de aplicación y lo devolverá.
  2. Cuando vuelves a llamar a la función getLocalPreferences () en otro lugar de la aplicación, primero verifica si no está disponible en la clase de la aplicación, que es mucho más rápida.

NOTA: SIEMPRE verifique si una variable en toda la aplicación es diferente de NULL, razón -> http://www.developerphil.com/dont-store-data-in-the-application-object/

El objeto de la aplicación no permanecerá en la memoria para siempre, será eliminado. Contrariamente a la creencia popular, la aplicación no se reiniciará desde cero. Android creará un nuevo objeto Aplicación e iniciará la actividad donde el usuario estaba antes para dar la ilusión de que la aplicación nunca fue eliminada en primer lugar.

Si no marcara el valor nulo, permitiría que se lanzara un puntero nulo al llamar, por ejemplo, getMaxDistance () en el objeto de filtro (si Android borró el objeto de la aplicación de la memoria)

Jdruwe
fuente
0

La clase SharedPreferences realiza algunas lecturas y escrituras dentro de los archivos XML en el disco, por lo que, al igual que cualquier otra operación de IO, podría estar bloqueando. La cantidad de datos almacenados actualmente en SharedPreferences afecta el tiempo y los recursos consumidos por las llamadas a la API. Para cantidades mínimas de datos, es cuestión de unos pocos milisegundos (a veces incluso menos de un milisegundo) obtener / colocar datos. Pero desde el punto de vista de un experto, podría ser importante mejorar el rendimiento haciendo las llamadas a la API en segundo plano. Para una SharedPreferences asincrónica, sugiero consultar la biblioteca Datum .

navid
fuente