2010-03-30 18 views
206

estoy registrar un oyente cambio de preferencia como esto (en el onCreate() de mi actividad principal):SharedPreferences.onSharedPreferenceChangeListener no siendo llamado constantemente

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 

prefs.registerOnSharedPreferenceChangeListener(
    new SharedPreferences.OnSharedPreferenceChangeListener() { 
     public void onSharedPreferenceChanged(
     SharedPreferences prefs, String key) { 

     System.out.println(key); 
     } 
}); 

El problema es que el oyente no siempre se llama. Funciona las primeras veces que se cambia una preferencia, y luego ya no se invoca hasta que desinstalo y vuelvo a instalar la aplicación. Ninguna cantidad de reinicio de la aplicación parece solucionarlo.

Encontré una lista de correo thread informando el mismo problema, pero nadie realmente le respondió. ¿Qué estoy haciendo mal?

Respuesta

509

Esto es furtivo. SharedPreferences mantiene a los oyentes en un WeakHashMap. Esto significa que no puede usar una clase interna anónima como oyente, ya que se convertirá en el objetivo de la recolección de basura tan pronto como salga del alcance actual. Funcionará al principio, pero eventualmente, se recolectará la basura, se eliminará de WeakHashMap y dejará de funcionar.

Mantenga una referencia al oyente en un campo de su clase y estará bien, siempre que su instancia de clase no se destruya.

es decir, en lugar de:

prefs.registerOnSharedPreferenceChangeListener(
    new SharedPreferences.OnSharedPreferenceChangeListener() { 
    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 
    // Implementation 
    } 
}); 

hacer esto:

// Use instance field for listener 
// It will not be gc'd as long as this instance is kept referenced 
listener = new SharedPreferences.OnSharedPreferenceChangeListener() { 
    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 
    // Implementation 
    } 
}; 

prefs.registerOnSharedPreferenceChangeListener(listener); 

La razón por la anulación del registro en el método OnDestroy soluciona el problema se debe a que ver que había que salvar al oyente en un campo, por lo tanto, prevenir el problema. Es salvar al oyente en un campo que soluciona el problema, no anular el registro en onDestroy.

ACTUALIZACIÓN: Los documentos de Android han sido updated con warnings sobre este comportamiento. Entonces, el comportamiento extraño permanece. Pero ahora está documentado.

+10

Esto me estaba matando, pensé que estaba perdiendo la cabeza. ¡Gracias por publicar esta solución! –

+7

¡Esta publicación es GRANDE, muchas gracias, esto podría haberme costado horas de depuración imposible! –

+0

Impresionante, esto es justo lo que necesitaba. Buena explicación también! – stealthcopter

11

esta respuesta aceptada es aceptable, ya que para mí es la creación de nueva instancia cada vez que se reanude la actividad

Entonces, ¿cómo trata de mantener la referencia al oyente dentro de la actividad

OnSharedPreferenceChangeListener myPrefListner = new OnSharedPreferenceChangeListener(){ 
     public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 
     // your stuff 
     } 
}; 

y en su onResume y onPause

@Override  
protected void onResume() { 
    super.onResume();   
    getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(myPrefListner);  
} 



@Override  
protected void onPause() {   
    super.onPause();   
    getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(myPrefListner); 

} 

esto será muy similar a lo que está haciendo, excepto que estamos manteniendo un d referencia.

+0

¿Por qué usaste 'super.onResume()' antes de 'getPreferenceScreen() ...'? –

+0

@YoushaAleayoub, lea sobre ** android.app.supernotcalledexception ** es requerido por la implementación de Android. – Samuel

+0

¿qué quieres decir? es necesario usar 'super.onResume()' O usarlo ANTES de que 'getPreferenceScreen() 'se requiera? porque estoy hablando del lugar correcto. http://cs.dartmouth.edu/~campbell/cs65/lecture05/lecture05.html –

15

Como esta es la página más detallada para el tema, quiero agregar mi 50ct.

Tuve el problema de que OnSharedPreferenceChangeListener no fue llamado.Mis SharedPreferences se recuperan en el inicio de la actividad principal por:

prefs = PreferenceManager.getDefaultSharedPreferences(this); 

Mi código PreferenceActivity es corta y no hace nada, excepto que muestra las preferencias:

public class Preferences extends PreferenceActivity { 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     // load the XML preferences file 
     addPreferencesFromResource(R.xml.preferences); 
    } 
} 

Cada vez que se pulsa el botón de menú se crea la PreferenceActivity de la Actividad principal:

@Override 
public boolean onPrepareOptionsMenu(Menu menu) { 
    super.onCreateOptionsMenu(menu); 
    //start Preference activity to show preferences on screen 
    startActivity(new Intent(this, Preferences.class)); 
    //hook into sharedPreferences. THIS NEEDS TO BE DONE AFTER CREATING THE ACTIVITY!!! 
    prefs.registerOnSharedPreferenceChangeListener(this); 
    return false; 
} 

Nota que el registro de la OnSharedPrefer enceChangeListener debe hacerse DESPUÉS de crear la actividad de preferencia en este caso, de lo contrario no se llamará al controlador en la actividad principal. Me llevó algún tiempo dulce para darse cuenta de que ...

+1

Gracias !!!!!! – RyanS

-2

Durante la lectura de la palabra de datos legibles compartidas por primera aplicación, debemos

Reemplazar

getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE); 

con

getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS); 

en segundo aplicación para obtener el valor actualizado en la segunda aplicación.

Pero todavía no está funcionando ...

+0

Android no admite el acceso a SharedPreferences desde múltiples procesos. Hacerlo causa problemas de concurrencia, lo que puede ocasionar la pérdida de todas las preferencias. Además, MODE_MULTI_PROCESS ya no es compatible. – Sam

+0

@Sam esta respuesta tiene 3 años de antigüedad, por favor, no lo vote, si no funciona para usted en las últimas versiones de Android. la respuesta del tiempo se escribió que era el mejor enfoque para hacerlo. –

+1

No, ese enfoque nunca fue seguro en múltiples procesos, incluso cuando escribió esta respuesta. – Sam

0

tiene sentido que los oyentes se mantienen en WeakHashMap.Because la mayor parte del tiempo, los desarrolladores prefieren escribir el código como este.

PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).registerOnSharedPreferenceChangeListener(
    new OnSharedPreferenceChangeListener() { 
    @Override 
    public void onSharedPreferenceChanged(
     SharedPreferences sharedPreferences, String key) { 
     Log.i(LOGTAG, "testOnSharedPreferenceChangedWrong key =" + key); 
    } 
}); 

Esto puede parecer no está mal. Pero si el contenedor OnSharedPreferenceChangeListeners no era WeakHashMap, sería muy malo. Si el código anterior se escribió en una Actividad. Dado que está utilizando una clase interna no estática (anónima) que contendrá implícitamente la referencia de la instancia circundante. Esto causará la pérdida de memoria.

Lo que es más, si mantiene al oyente como un campo, se puede usar registerOnSharedPreferenceChangeListener al comienzo y al llamar unregisterOnSharedPreferenceChangeListener al final. Pero no puede acceder a una variable local en un método fuera de su alcance. Entonces solo tienes la oportunidad de registrarte pero no tienes la oportunidad de anular el registro del oyente. Por lo tanto, usar WeakHashMap resolverá el problema. Esta es la manera que recomiendo.

Si convierte la instancia del oyente en un campo estático, evitará la pérdida de memoria causada por la clase interna no estática. Pero como los oyentes pueden ser múltiples, debe estar relacionado con la instancia. Esto reducirá el costo de manejo de la devolución de llamada onSharedPreferenceChanged.

Cuestiones relacionadas