web-dev-qa-db-de.com

SharedPreferences.onSharedPreferenceChangeListener wird nicht konsistent aufgerufen

Ich registriere einen Präferenzänderungslistener wie folgt (in der onCreate() meiner Hauptaktivität):

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

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

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

Das Problem ist, der Zuhörer wird nicht immer gerufen. Es funktioniert zum ersten Mal, wenn eine Einstellung geändert wird, und es wird nicht mehr aufgerufen, bis ich die App deinstalliere und erneut installiere. Kein Neustart der Anwendung scheint das Problem zu beheben.

Ich habe eine Mailingliste thread gefunden, die das gleiche Problem gemeldet hat, aber niemand hat ihm wirklich geantwortet. Was mache ich falsch?

234
synic

Dies ist eine hinterlistige. SharedPreferences hält Zuhörer in einer WeakHashMap. Das bedeutet, dass Sie keine anonyme innere Klasse als Listener verwenden können, da diese zum Ziel der Garbage Collection wird, sobald Sie den aktuellen Bereich verlassen. Es wird zuerst funktionieren, aber irgendwann wird Müll gesammelt, aus der WeakHashMap entfernt und funktioniert nicht mehr.

Behalten Sie einen Verweis auf den Listener in einem Feld Ihrer Klasse, und Sie sind in Ordnung, vorausgesetzt, Ihre Klasseninstanz wird nicht zerstört.

anstelle von:

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

mach das:

// 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);

Der Grund, warum die Aufhebung der Registrierung in der onDestroy-Methode das Problem behebt, liegt darin, dass Sie den Listener in einem Feld speichern mussten, um das Problem zu vermeiden. Es ist das Speichern des Listeners in einem Feld, das das Problem behebt, nicht die Aufhebung der Registrierung in onDestroy.

UPDATE: Die Android-Dokumente wurden aktualisiert mit Warnungen über dieses Verhalten. So bleibt seltsames Verhalten. Jetzt ist es dokumentiert.

558
Blanka

Da dies die detaillierteste Seite für das Thema ist, möchte ich meinen 50ct hinzufügen.

Ich hatte das Problem, dass OnSharedPreferenceChangeListener nicht aufgerufen wurde. Meine SharedPreferences werden zu Beginn der Hauptaktivität abgerufen durch:

prefs = PreferenceManager.getDefaultSharedPreferences(this);

Mein PreferenceActivity-Code ist kurz und zeigt nur die Präferenzen:

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

Bei jedem Drücken der Menütaste erstelle ich die PreferenceActivity aus der Hauptaktivität:

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

Hinweis dass das Registrieren des OnSharedPreferenceChangeListener NACH dem Erstellen der PreferenceActivity in diesem Fall erfolgen muss, andernfalls wird der Handler in der Hauptaktivität nicht aufgerufen !!! Ich brauchte einige Zeit, um zu realisieren, dass ...

15
Bim

diese akzeptierte Antwort ist in Ordnung, da bei mir jedes Mal, wenn die Aktivität fortgesetzt wird, eine neue Instanz erstellt wird

wie wäre es also, wenn Sie den Verweis auf den Hörer innerhalb der Aktivität beibehalten

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

und in Ihrem onResume und onPause

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



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

}

dies wird dem, was Sie tun, sehr ähnlich sein, außer wir halten einen harten Bezug.

13
Samuel

Die akzeptierte Antwort erstellt bei jedem Aufruf von onResume () einen SharedPreferenceChangeListener. @Samuel löst es, indem SharedPreferenceListener ein Mitglied der Activity-Klasse wird. Es gibt jedoch eine dritte und eine einfachere Lösung, die Google auch in diesem Codelab verwendet. Lassen Sie Ihre Aktivitätsklasse die OnSharedPreferenceChangeListener-Schnittstelle implementieren und onSharedPreferenceChanged in der Aktivität überschreiben, wodurch die Aktivität selbst zum SharedPreferenceListener wird.

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

Ich weiß also nicht, ob dies wirklich jemandem helfen würde, es hat mein Problem gelöst. Obwohl ich die Variable OnSharedPreferenceChangeListener implementiert hatte, wie in der akzeptierten Antwort angegeben. Trotzdem hatte ich eine Inkonsistenz mit dem aufgerufenen Hörer.

Ich kam hierher, um zu verstehen, dass das Android es nach einiger Zeit nur zur Müllsammlung sendet. Also schaute ich mir meinen Code an ... _. Zu meiner Schande hatte ich nicht den ListenerGLOBAL, sondern stattdessen innerhalb der onCreateView deklariert. Und das war so, weil ich mir das Android Studio angehört habe, in dem ich aufgefordert wurde, den Listener in eine lokale Variable zu konvertieren.

1
Asghar Musani

Kotlin-Code für das Register SharedPreferenceChangeListener, das erkennt, wenn der gespeicherte Schlüssel geändert wird:

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

sie können diesen Code in onStart () oder an anderer Stelle ... eingeben. * Beachten Sie, dass Sie verwenden müssen 

 if(key=="YourKey")

oder Ihre Codes in // Do Something-Block werden bei jeder Änderung, die in einem anderen Schlüssel in sharedPreferences ausgeführt wird, falsch ausgeführt

0
Hamed Jaliliani

Es ist sinnvoll, dass die Listener in WeakHashMap gespeichert werden. Da die meiste Zeit Entwickler vorziehen, den Code so zu schreiben.

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

Das mag nicht schlimm erscheinen. Wenn der Container der OnSharedPreferenceChangeListeners nicht WeakHashMap wäre, wäre dies sehr schlecht. Wenn der obige Code in eine Activity geschrieben wurde. Da Sie eine nicht statische (anonyme) innere Klasse verwenden, die implizit die Referenz der umgebenden Instanz enthält. Dies führt zu Speicherverlust.

Wenn Sie den Listener als Feld beibehalten, können Sie am Anfang registerOnSharedPreferenceChangeListener verwenden und unregisterOnSharedPreferenceChangeListener aufrufen. Sie können jedoch nicht auf eine lokale Variable in einer Methode außerhalb ihres Gültigkeitsbereichs zugreifen. Sie haben also nur die Möglichkeit, sich zu registrieren, aber Sie können den Listener nicht abmelden. Wenn Sie also WeakHashMap verwenden, wird das Problem behoben. So empfehle ich es.

Wenn Sie die Listener-Instanz als statisches Feld erstellen, wird der Speicherverlust durch nicht statische innere Klasse vermieden. Da die Listener jedoch mehrere sein können, sollte es sich auf Instanzen beziehen. Dadurch werden die Kosten für die Behandlung von onSharedPreferenceChanged callback reduziert.

0
androidyue