294 votes

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

J'enregistre un écouteur de changement de préférence comme ceci (dans le fichier 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'écouteur n'est pas toujours appelé. Il fonctionne les premières fois qu'une préférence est modifiée, puis il n'est plus appelé jusqu'à ce que je désinstalle et réinstalle l'application. Le redémarrage de l'application ne semble pas résoudre le problème.

J'ai trouvé une liste de diffusion filetage rapportant le même problème, mais personne ne lui a vraiment répondu. Qu'est-ce que je fais de mal ?

679voto

Blanka Points 3592

Il est sournois. SharedPreferences conserve les écouteurs dans une WeakHashMap. Cela signifie que vous ne pouvez pas utiliser une classe interne anonyme comme écouteur, car elle deviendra la cible de la collecte des déchets dès que vous quitterez la portée actuelle. Elle fonctionnera au début, mais finira par faire l'objet d'un ramassage, sera supprimée de la WeakHashMap et cessera de fonctionner.

Gardez une référence à l'écouteur dans un champ de votre classe et tout ira bien, à condition que l'instance de votre 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
  }
});

faites-le :

// 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 désenregistrement dans la méthode onDestroy résout le problème est que pour ce faire, vous avez dû enregistrer l'écouteur dans un champ, ce qui a empêché le problème. C'est l'enregistrement de l'écouteur dans un champ qui résout le problème, pas le désenregistrement dans la méthode onDestroy.

UPDATE : La documentation Android a été actualisé con avertissements sur ce comportement. Donc, le comportement bizarre demeure. Mais maintenant c'est documenté.

35 votes

Ça me tuait, je pensais que je perdais la tête. Merci d'avoir posté cette solution !

0 votes

Cela ne fonctionne pas pour moi. Existe-t-il un moyen de le faire fonctionner si la SharedPreference est utilisée par deux applications ? J'ai une application qui effectue le changement et une autre qui y réagit. La seule façon de le faire fonctionner était d'utiliser le modèle IPC et d'envoyer une diffusion d'un APK à un autre.

0 votes

J'ai fait la même chose que vous avez répondu @Blanka mais après avoir fermé l'application, la liste ne fonctionne pas. Je n'ai pas désenregistré la liste dans la méthode onDestroy().

24voto

Samuel Points 4472

Cette réponse acceptée est correcte, car pour moi, elle est en train de créer nouvelle instance chaque fois que l'activité reprend

alors comment conserver la référence à l'écouteur dans l'activité ?

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

et dans vos onResume et onPause

@Override     
public void onResume() {
    super.onResume();          
    getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);     
}

@Override     
public void onPause() {         
    super.onPause();          
    getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);

}

Ceci est très similaire à ce que vous faites, sauf que nous maintenons une référence dure.

0 votes

Pourquoi vous avez utilisé super.onResume() avant getPreferenceScreen()... ?

0 votes

@YoushaAleayoub, lire à ce sujet Android.app.supernotcalledexception elle est requise par l'implémentation d'Android.

0 votes

Que voulez-vous dire ? en utilisant super.onResume() est nécessaire OU l'utiliser AVANT getPreferenceScreen() est nécessaire ? parce que je parle du BON endroit. cs.dartmouth.edu/~campbell/cs65/lecture05/lecture05.html

16voto

Bim Points 91

Comme il s'agit de la page la plus détaillée sur le sujet, je souhaite ajouter mes 50ct.

J'ai eu le problème que le OnSharedPreferenceChangeListener n'était pas appelé. Mes SharedPreference sont récupérées au début de l'activité principale par :

prefs = PreferenceManager.getDefaultSharedPreferences(this);

Mon code PreferenceActivity est court et ne fait rien d'autre que d'afficher 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 l'on appuie sur le bouton de menu, je crée la PreferenceActivity à 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;
}

Note que l'enregistrement du OnSharedPreferenceChangeListener doit être fait APRES la création de la PreferenceActivity dans ce cas, sinon le Handler dans l'activité principale ne sera pas appelé !!! Il m'a fallu un certain temps pour réaliser cela...

3voto

CommonsWare Points 402670

Assurez-vous que vous désenregistrez le listener (par exemple, dans la section onDestroy() ).

De plus, vous pourriez vouloir passer à l'utilisation de android.util.Log au lieu de System.out pour votre enregistrement.

En dehors de cela, je ne vois rien de mal à ce que vous avez.

-3voto

shridutt kothari Points 2038

En lisant les données lisibles par le mot partagées par la première application, nous devons

Remplacer

getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);

con

getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);

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

Mais cela ne fonctionne toujours pas...

0 votes

Android ne prend pas en charge l'accès aux préférences partagées à partir de plusieurs processus. Cela pose des problèmes de concurrence, ce qui peut entraîner la perte de toutes les préférences. En outre, MODE_MULTI_PROCESS n'est plus pris en charge.

0 votes

@Sam cette réponse est vieille de 3 ans, s'il vous plaît ne la votez pas, si elle ne fonctionne pas pour vous dans les dernières versions d'Android. au moment où les réponses ont été écrites c'était la meilleure approche pour le faire.

1 votes

Non, cette approche n'a jamais été sûre pour les processus multiples, même lorsque vous avez écrit cette réponse.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X