SharedPreferences.onSharedPreferenceChangeListener ne s'appelle pas toujours

j'enregistre un auditeur de changement de préférence comme ceci (dans le 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é, puis il n'est plus appelé jusqu'à ce que je désinstaller et réinstaller l'application. Aucun redémarrage de l'application ne semble le corriger.

j'ai trouvé une liste de diffusion thread rapportant le même problème, mais personne ne lui répondit. Ce que je fais mal?

228
demandé sur ישו אוהב אותך 2010-03-30 08:55:36

7 réponses

celui-ci est sournois. SharedPreferences maintient les auditeurs dans une faible image. Cela signifie que vous ne pouvez pas utiliser une classe interne anonyme comme auditeur, car elle deviendra la cible de la collecte des ordures dès que vous quitterez la portée actuelle. Il fonctionnera d'abord, mais finalement, se fera ramasser les ordures, retiré de la carte de Faighhash et arrêter de travailler.

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

c'est à dire au lieu de:

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

faites 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 faire cela il fallait sauvegarder l'auditeur dans un champ, donc empêcher la question. C'est la sauvegarde de l'auditeur dans un champ qui règle le problème, pas la désinscription dans onDestroy.

mise à jour : L'Android docs ont été mis à jour avec Avertissements à propos de ce comportement. Donc, excentrique comportement reste. Mais maintenant, il est documenté.

543
répondu Blanka 2014-06-25 21:20:52

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

j'ai eu le problème que OnSharedPreferenceChangeListener n'a pas été appelé. Mes références partagées sont récupérées au début de L'activité principale par:

prefs = PreferenceManager.getDefaultSharedPreferences(this);

mon code de Préférenceactivité 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 le bouton menu est appuyé je crée la 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;
}

Note que l'enregistrement du OnSharedPreferenceChangeListener doit être fait après la création de L'activité de préférence dans ce cas, sinon le Handler dans l'activité principale ne sera pas appelé!!! Il m'a fallu du temps pour m'en rendre compte...

15
répondu Bim 2017-01-24 11:39:48

accepté cette réponse est ok, car pour moi c'est la création de nouvelle instance chaque fois que la 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);

}

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

13
répondu Samuel 2013-09-03 08:03:15

la réponse acceptée crée un SharedPreferenceChangeListener chaque fois qu'onResume() est appelé. @Samuel le résout en faisant de SharedPreferenceListener un membre de la classe D'activité. Mais il y a une troisième solution plus simple que Google utilise également dans ce codelab . Faites votre classe d'activité mettre en œuvre l'interface OnSharedPreferenceChangeListener et outrepasser onSharedPreferenceChanged dans L'activité, faisant effectivement L'activité elle-même 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);
    }
}
1
répondu PrashanD 2018-06-24 17:05:58

il est logique que les auditeurs soient maintenus dans WeakHashMap.Parce que la plupart du temps, les développeurs préfèrent écrire le code comme ceci.

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 container OnSharedPreferenceChangeListeners n'était pas WeakHashMap, il serait très mauvais.Si le code ci-dessus a été écrit dans une Activité . Puisque vous utilisez non-static (anonymous) inner class qui tiendra implicitement la référence de l'instance enveloppante. Ce sera la cause de fuite de mémoire.

qui plus est, si vous gardez l'auditeur comme un champ, vous pouvez utiliser registerOnSharedPreferenceChangelistener au début et appeler ununisteronsharedpreferencechangelistener à 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ésinscrire l'auditeur. Ainsi, L'utilisation de WeakHashMap résoudra le problème. C'est la façon dont je recommander.

si vous faites l'instance d'écoute comme un champ statique, elle évitera la fuite de mémoire causée par la classe interne non statique. Mais comme les écouteurs peuvent être multiples, cela devrait être lié à l'instance. Cela permettra de réduire le coût de traitement du rappel onsharedpreferencechangé .

0
répondu androidyue 2014-11-27 12:10:23

donc, je ne sais pas si cela pourrait vraiment aider quelqu'un cependant, il a résolu mon problème. Même si j'avais mis en œuvre le OnSharedPreferenceChangeListener comme indiqué par le réponse acceptée . Pourtant, j'ai eu une incohérence avec l'auditeur appelé.

je suis venu ici pour comprendre que L'Androïde envoie juste pour la collecte des ordures après un certain temps. Donc, j'ai regardé mon code. À ma honte, je n'avais pas déclaré l'auditeur global mais à l'intérieur du onCreateView . Et c'est parce que j'ai écouté le Studio Android me disant de convertir l'auditeur à une variable locale.

0
répondu Asghar Musani 2018-08-21 01:12:56

lors de la lecture de données lisibles par des mots partagés 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 la valeur mise à jour dans la deuxième application.

mais ça ne marche toujours pas...

-3
répondu shridutt kothari 2013-03-07 14:26:52