2010-12-06 14 views
90

Con el lanzamiento de Gingerbread, he estado experimentando con algunas de las nuevas API, una de ellas es StrictMode.¿Se debe acceder a las Preferencias Compartidas desde el subproceso UI?

Me di cuenta de 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 está siendo dada por una llamada getSharedPreferences() están realizando en el hilo de interfaz de usuario.

¿Debería el acceso y los cambios de SharedPreferences realmente hacerse fuera del hilo de la interfaz de usuario?

+0

I Siempre he hecho mis operaciones de preferencia en el hilo de UI. Aunque supongo que tiene sentido ya que es una operación IO – Falmarri

Respuesta

157

¡Me alegra que ya juegues con él!

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

  • si este es el peor de sus problemas, su aplicación es, probablemente, en un buen lugar. :) Las escrituras generalmente son más lentas que las lecturas, así que asegúrese de estar utilizando 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 reflection para llamar condicionalmente a apply() en GB + y commit() en Froyo o abajo. Haré una publicación de blog con código de muestra de cómo hacer esto.

respecta a la carga, aunque ...

  • una vez cargado, SharedPreferences son únicos y en caché proceso de ancho. por lo que desea cargarlo lo antes posible para que lo tenga en la memoria antes de que lo necesite. (suponiendo que es pequeño, como debería ser si está utilizando SharedPreferences, un archivo XML simple ...) No querrá fallar en el futuro si algún usuario hace clic en un botón.

  • pero siempre que llame a context.getSharedPreferences (...), el archivo XML de respaldo está listo para ver si ha cambiado, por lo que querrá evitar esas estadísticas durante los eventos de IU de todos modos. Una estadística normalmente debería ser rápida (y a menudo almacenada en caché), pero yaffs no tiene demasiada concurrencia (y muchos dispositivos Android corren en yaffs ... Droid, Nexus One, etc.) así que si evitas el disco , evita evitar atascarse detrás de otras operaciones de disco pendientes o en vuelo.

  • por lo que probablemente desee cargar las Preferencias Compartidas durante su onCreate() y reutilizar la misma instancia, evitando la estadística.

  • pero si usted no necesita sus preferencias de todos modos durante onCreate(), que el tiempo de carga está estancando la puesta en marcha de su aplicación sin necesidad, por lo que es generalmente mejor tener algo así como un FutureTask <SharedPreferences> subclase que inicia una nuevo hilo para .set() el valor de las subclases FutureTask. A continuación, busque su miembro FutureTask <SharedPreferences> cada vez que lo necesite y .get(). Planeo hacer esto gratis detrás de escena en Honeycomb, de forma transparente. Trataré de liberar algún código de muestra que muestra las mejores prácticas en esta área.

Mira el blog de Desarrolladores de Android para próximas publicaciones sobre temas relacionados con StrictMode en la (s) próxima (s) semana (s).

+0

¡Guau, no esperaba obtener una respuesta tan clara directamente de la fuente! ¡Muchas gracias! – cottonBallPaws

+6

Para el beneficio de los nuevos lectores de esta maravillosa publicación, busque debajo el enlace a la publicación de blog mencionada anteriormente por @Brad Fitzpatrick: [publicación del blog del desarrollador de Android en modo estricto por Brad] (http://android-developers.blogspot.de /2010/12/new-gingerbread-api-strictmode.html). La publicación también tiene un enlace al código de muestra para usar apply (desde gingerbread en adelante) o commit (froyo) basado en la versión de Android para almacenar preferencias compartidas: [condicionalmente use apply o commit] (https://code.google.com/p /zippy-android/source/browse/trunk/examples/SharedPreferencesCompat.java) –

+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? Tal vez podría usar un formato diferente, por ejemplo, una base de datos SQLite.

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

Pero no se siente obligado a arreglar todo lo que StrictMode encuentra. En particular, muchos casos de acceso a disco a menudo son necesarios durante el ciclo de vida de la actividad normal. Usa StrictMode para encontrar cosas que hiciste por accidente. Sin embargo, las solicitudes de red en el hilo de la interfaz de usuario casi siempre son un problema.

+5

Pero no es el SQLite también un archivo que debe leerse desde el almacenamiento flash, pero uno más grande y más 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

+0

Eso es correcto. Como Brad ya mencionó, esto casi siempre no es problema, y ​​también menciona que es una buena idea cargar las SharedPreferences una vez (quizás incluso en un Thread usando FutureTask) y mantenerlas para cualquier posible acceso a la instancia única. – mreichelt

3

Una sutileza sobre la respuesta de Brad: incluso si carga las Preferencias Compartidas en onCreate(), probablemente debería seguir leyendo los valores en la cadena de fondo porque getString() etc. bloquea hasta leer la preferencia de archivos compartidos en los acabados (en un fondo hilo):

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

edición() también bloquea la misma manera, a pesar de aplicar() parece ser segura en el subproceso de primer plano.

(Por cierto siento a poner esto aquí. Me he puesto esto como un comentario a la respuesta de Brad, pero me acaba de unirse y no tienen la reputación suficiente para hacerlo.)

1

Sé que esto es una vieja pregunta, pero quiero compartir mi enfoque. Había largos tiempos de lectura y se utiliza una combinación de preferencias compartidas y la clase de aplicación mundial:

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 consiga llamar por primera vez en su aplicación):

LocalPreference.getLocalPreferences(this); 

Pasos explicados:

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

NOTA: Compruebe siempre que una amplia variable de aplicación es diferente de NULL, la razón ->http://www.developerphil.com/dont-store-data-in-the-application-object/

El objeto de la aplicación no se quedará en la memoria para siempre, va a ser asesinado. Contrario 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 se mató en primer lugar.

Si no me registro en nulo que permitiría una NullPointer a ser lanzado al llamar por ejemplo getMaxDistance() en el objeto de filtro (si el objeto de la aplicación se limpió de la memoria por Android)

Cuestiones relacionadas