SharedPreferences.onSharedPreferenceChangeListener n'est pas appelé de manière cohérente

Je enregistre un auditeur de changement de préférence comme celui-ci (dans onCreate() de mon activité principale):

 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); prefs.registerOnSharedPreferenceChangeListener( new SharedPreferences.OnSharedPreferenceChangeListener() { public void onSharedPreferenceChanged( SharedPreferences prefs, String key) { System.out.println(key); } }); 

Le problème est que l'auditeur n'est pas toujours appelé. Il fonctionne pour les premières fois, une préférence est modifiée, puis il n'est plus appelé jusqu'à ce que je désinstalle et réinstalle l'application. Aucune quantité de redémarrage de la demande ne semble la résoudre.

  • Pourquoi une classe fragmentaire pourrait-elle ne pas être valide?
  • Charger une chaîne spécifique à la langue à partir de la ressource?
  • Android: Activez le réglage de l'état de rotation de l'accéléromètre
  • Eclipse LogCat affiche uniquement la première lettre de chaque message
  • Types MIME multiples dans Android
  • Phonegap 3.0 - Android: barre de défilement native non visible
  • J'ai trouvé un thread de liste de diffusion rapportant le même problème, mais personne ne l'a vraiment répondu. Qu'est-ce que je fais mal?

  • Comment modifier par programme les couleurs d'arrière-plan des éléments de la barre d'action
  • R.id.container non résolu
  • Comment créer deux vues dans Android qui utilisent 50% de hauteur chacune, sauf si une est plus petite?
  • Détecter quand une autre application a été ouverte ou lancée
  • Sujets sur la messagerie Cloud Firebase?
  • Panne d'autorisation de carte API v2
  • 5 Solutions collect form web for “SharedPreferences.onSharedPreferenceChangeListener n'est pas appelé de manière cohérente”

    C'est sournois. SharedPreferences garde les auditeurs dans un WeakHashMap. Cela signifie que vous ne pouvez pas utiliser une classe interne anonyme comme un auditeur, car elle deviendra la cible de la collecte des ordures dès que vous quitterez la portée actuelle. Il fonctionnera tout d'abord, mais éventuellement obtiendra les ordures collectées, retiré du WeakHashMap et cessera de fonctionner.

    Gardez une référence à l'auditeur dans un champ de votre classe et vous serez OK, à condition que votre instance de classe ne soit pas détruite.

    C'est-à-dire au lieu de:

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

    faire cela:

     // 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 raison pour laquelle le non enregistrement de la méthode onDestroy corrige le problème est dû au fait que vous devez enregistrer l'auditeur dans un champ, ce qui empêche le problème. C'est la sauvegarde de l'auditeur dans un domaine qui corrige le problème, et non le désenregistrement de onDestroy.

    MISE À JOUR : les documents Android ont été mis à jour avec des avertissements concernant ce comportement. Donc, le comportement étrange reste. Mais maintenant, il est documenté.

    Comme c'est la page la plus détaillée pour le sujet, je souhaite ajouter mon 50ct.

    J'ai eu le problème que OnSharedPreferenceChangeListener n'a pas été appelé. Mes paramètres partagés sont récupérés au début de l'activité principale par:

     prefs = PreferenceManager.getDefaultSharedPreferences(this); 

    Mon code PreferenceActivity est court et ne fait rien sauf en montrant les préférences:

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

    Chaque fois que vous appuyez sur le bouton de menu, je crée la PréférenceActivité à partir de l'Activité principale:

     @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; } 

    Notez que l'enregistrement de OnSharedPreferenceChangeListener doit être effectué après la création de PreferenceActivity dans ce cas, sinon le gestionnaire dans l'activité principale ne sera pas appelé !!! Il m'a fallu un bon moment pour me rendre compte …

    Cette réponse acceptée est correcte, car je crée une nouvelle instance chaque fois que l'activité reprend

    Alors, qu'en est-il de garder la référence à l'auditeur dans l'activité

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

    Et dans votre onResume et onPause

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

    Cela sera très similaire à ce que vous faites, sauf que nous maintenons une référence difficile.

    En lisant les données lisibles par Word partagées par la première application, nous devrions

    Remplacer

     getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE); 

    avec

     getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS); 

    Dans la deuxième application pour obtenir une valeur mise à jour dans la seconde application.

    Mais cela ne fonctionne toujours pas …

    Il est logique que les auditeurs soient conservés dans WeakHashMap. Pour la plupart du temps, les développeurs préfèrent écrire le code comme celui-ci.

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

    Cela peut sembler mal. Mais si le conteneur OnSharedPreferenceChangeListeners n'était pas WeakHashMap, il serait très mauvais. Si le code ci-dessus a été écrit dans une Activité. Étant donné que vous utilisez une classe intérieure non-statique (anonyme) qui contiendra implicitement la référence de l'instance jointe. Cela entraînera une fuite de mémoire.

    De plus, si vous gardez l'auditeur comme champ, vous pouvez utiliser registerOnSharedPreferenceChangeListener au début et appeler unregisterOnSharedPreferenceChangeListener à la fin. Mais vous ne pouvez pas accéder à une variable locale dans une méthode hors de sa portée. Donc, vous avez juste l'opportunité de vous enregistrer, mais pas de chance de vous désinscrire de l'auditeur. Ainsi, l'utilisation de WeakHashMap résoudra le problème. C'est ce que je recommande.

    Si vous créez l'instance de l'auditeur comme un champ statique, cela évitera la fuite de mémoire causée par une classe interne non statique. Mais comme les auditeurs pourraient être multiples, cela devrait être lié à l'instance. Cela réduira le coût de la gestion du rappel OnSharedPreferenceChanged .

    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.