web-dev-qa-db-fra.com

SharedPreferences.onSharedPreferenceChangeListener non appelé de manière cohérente

J'inscris un auditeur de changement de préférence comme ceci (dans la 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é. Cela fonctionne les premières fois où une préférence est modifiée, puis elle n'est plus appelée jusqu'à ce que je désinstalle et réinstalle l'application. Aucune quantité de redémarrage de l'application ne semble la résoudre.

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

234
synic

Ceci est un sournois. SharedPreferences conserve les auditeurs dans un WeakHashMap. Cela signifie que vous ne pouvez pas utiliser une classe interne anonyme en tant qu'écouteur, car elle deviendra la cible du garbage collection dès que vous quitterez la portée actuelle. Cela fonctionnera au début, mais éventuellement, les ordures seront collectées, retirées du WeakHashMap et cesseront de fonctionner.

Conservez une référence à l'auditeur dans un champ de votre classe et tout ira bien si votre instance de classe n'est pas détruite.

c'est-à-dire au lieu de:

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

faire ceci:

// 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 la désinscription dans la méthode onDestroy corrige le problème est que, pour ce faire, vous deviez enregistrer le programme d'écoute dans un champ, ce qui empêchait le problème. C'est la sauvegarde de l'auditeur dans un champ qui résout le problème, pas la désinscription dans onDestroy.

UPDATE: les documents Android ont été mis à jour avec warnings à propos de ce comportement. Donc, le comportement étrange reste. Mais maintenant, c'est documenté.

558
Blanka

Comme il s’agit de 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 préférences partagées 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 sauf 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 vous appuyez sur le bouton de menu, je crée l’activité PreferenceActivity 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;
}

Remarque que l'enregistrement de OnSharedPreferenceChangeListener doit être effectué APRÈS la création de PreferenceActivity dans ce cas, sinon le Handler de l'activité principale ne sera pas appelé !!! Il m'a fallu du temps pour me rendre compte que ...

15
Bim

cette réponse acceptée est ok, car pour moi, elle crée nouvelle instance à chaque reprise de l'activité

alors que diriez-vous 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 ressemblera beaucoup à ce que vous faites sauf que nous maintenons une référence dure.

13
Samuel

La réponse acceptée crée un SharedPreferenceChangeListener chaque fois que l'onResume () est appelé. @Samuel le résout en faisant de SharedPreferenceListener un membre de la classe Activity. Mais il existe une troisième solution, plus simple, que Google utilise également dans this codelab. Faites en sorte que votre classe d'activité implémente l'interface OnSharedPreferenceChangeListener et remplace onSharedPreferenceChanged dans l'activité, transformant ainsi l'activité elle-même en un SharedPreferenceListener.

public class MainActivity extends Activity implements SharedPreferences.OnSharedPreferenceChangeListener {

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {

    }

    @Override
    protected void onStart() {
        super.onStart();
        PreferenceManager.getDefaultSharedPreferences(this)
                .registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        PreferenceManager.getDefaultSharedPreferences(this)
                .unregisterOnSharedPreferenceChangeListener(this);
    }
}
2
PrashanD

Donc, je ne sais pas si cela aiderait vraiment quelqu'un cependant, cela a résolu mon problème… .. Même si j'avais implémenté la OnSharedPreferenceChangeListener comme indiqué par le réponse acceptée . Pourtant, j'avais une incohérence avec l'auditeur appelé.

Je suis venu ici pour comprendre que l’Android l’envoyait simplement pour la collecte des ordures au bout d’un certain temps. Alors, j'ai regardé mon code… .. À ma grande honte, je n'avais pas déclaré l'auditeurGLOBALEMENTmais à la place de la onCreateView. Et c’est parce que j’ai écouté Android Studio me disant de convertir l’auditeur en une variable locale.

1
Asghar Musani

Code Kotlin pour le registre SharedPreferenceChangeListener détecte quand le changement se produira sur la clé enregistrée:

  PreferenceManager.getDefaultSharedPreferences(this)
        .registerOnSharedPreferenceChangeListener { sharedPreferences, key ->
            if(key=="language") {
                //Do Something 
            }
        }

vous pouvez mettre ce code dans onStart (), ou ailleurs .. * Considérez que vous devez utiliser 

 if(key=="YourKey")

ou vos codes dans // le blocage Quelque chose ne sera pas exécuté correctement à chaque changement qui se produira dans une autre clé dans les préférences partagées

0
Hamed Jaliliani

Il est logique que les écouteurs soient conservés dans WeakHashMap. Parce que la plupart du temps, les développeurs préfèrent écrire le code de cette manière.

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

Cela peut sembler pas mal. Mais si le conteneur de OnSharedPreferenceChangeListeners n'était pas WeakHashMap, ce serait très mauvais. Si le code ci-dessus était écrit dans une activité. Puisque vous utilisez une classe interne non statique (anonyme), elle contiendra implicitement la référence de l'instance englobante. Cela provoquera une fuite de mémoire.

De plus, si vous conservez l'écouteur en tant que 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 la possibilité de vous inscrire, mais aucune chance de désenregistrer l'auditeur. L'utilisation de WeakHashMap résoudra donc le problème. C'est la façon que je recommande.

Si vous créez l'instance du programme d'écoute sous la forme d'un champ statique, cela évitera la fuite de mémoire provoquée par la classe interne non statique. Mais comme les auditeurs peuvent être multiples, cela devrait être lié à l'instance. Cela réduira le coût de traitement du rappel onSharedPreferenceChanged.

0
androidyue