SharedPreferences.onSharedPreferenceChangeListener wordt niet consistent aangeroepen

Ik registreer een luisteraar voor het wijzigen van voorkeuren zoals deze (in de onCreate()van mijn hoofdactiviteit):

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

Het probleem is dat de luisteraar niet altijd wordt gebeld. Het werkt de eerste paar keer dat een voorkeur wordt gewijzigd, en daarna wordt het niet meer aangeroepen totdat ik de app verwijder en opnieuw installeer. Het opnieuw opstarten van de applicatie lijkt het niet te verhelpen.

Ik heb een mailinglijst threadgevonden die de hetzelfde probleem, maar niemand antwoordde hem echt. Wat doe ik verkeerd?


Antwoord 1, autoriteit 100%

Dit is een stiekeme. SharedPreferences houdt luisteraars in een WeakHashMap. Dit betekent dat je geen anonieme innerlijke klasse als luisteraar kunt gebruiken, omdat het het doelwit wordt van afvalinzameling zodra je het huidige bereik verlaat. Het zal eerst werken, maar uiteindelijk wordt het afval verzameld, verwijderd uit de WeakHashMap en stopt het met werken.

Houd een verwijzing naar de luisteraar in een veld van je klas en je zult OK zijn, op voorwaarde dat je klasinstantie niet wordt vernietigd.

d.w.z. in plaats van:

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

doe dit:

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

De reden dat het ongedaan maken van de registratie in de onDestroy-methode het probleem oplost, is dat je daarvoor de listener in een veld moest opslaan, waardoor het probleem werd voorkomen. Het is het opslaan van de luisteraar in een veld dat het probleem oplost, niet het uitschrijven in onDestroy.

UPDATE: de Android-documenten zijn bijgewerktmet waarschuwingenover dit gedrag. Dus, excentriek gedrag blijft. Maar nu is het gedocumenteerd.


Antwoord 2, autoriteit 3%

dit geaccepteerde antwoord is oké, omdat het voor mij een nieuwe instantiemaakt elke keer dat de activiteit wordt hervat

dus hoe zit het met het houden van de verwijzing naar de luisteraar binnen de activiteit

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

en in je onResume en onPause

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

dit zal erg lijken op wat u doet, behalve dat we een harde referentie onderhouden.


Antwoord 3, autoriteit 2%

Omdat dit de meest gedetailleerde pagina is voor het onderwerp, wil ik mijn 50ct toevoegen.

Ik had het probleem dat OnSharedPreferenceChangeListener niet werd aangeroepen. Mijn SharedPreferences worden opgehaald aan het begin van de hoofdactiviteit door:

prefs = PreferenceManager.getDefaultSharedPreferences(this);

Mijn PreferenceActivity-code is kort en doet niets anders dan de voorkeuren weergeven:

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

Elke keer dat de menuknop wordt ingedrukt, maak ik de Voorkeursactiviteit aan vanuit de hoofdactiviteit:

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

Merk opdat het registreren van de OnSharedPreferenceChangeListener moet worden gedaan NA het maken van de PreferenceActivity in dit geval, anders wordt de Handler in de hoofdactiviteit niet aangeroepen!!! Het kostte me wat tijd om te beseffen dat…


Antwoord 4, autoriteit 2%

Het geaccepteerde antwoord creëert een SharedPreferenceChangeListenerelke keer dat onResumewordt aangeroepen. @Samuellost het op door SharedPreferenceListenerlid te maken van de Activity-klasse. Maar er is een derde en eenvoudigere oplossing die Googleook gebruikt in dit codelab. Laat uw activiteitsklasse de OnSharedPreferenceChangeListener-interface implementeren en onSharedPreferenceChangedoverschrijven in de Activiteit, waardoor de Activiteit zelf in feite een SharedPreferenceListenerwordt.

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

Antwoord 5

Kotlin-code voor register SharedPreferenceChangeListener het detecteert wanneer er verandering zal plaatsvinden op de opgeslagen sleutel:

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

je kunt deze code in onStart() plaatsen, of ergens anders..
*Bedenk dat u

. moet gebruiken

if(key=="YourKey")

of uw codes in het blok “//Do Something” worden verkeerd uitgevoerd voor elke wijziging die plaatsvindt in een andere sleutel in sharedPreferences


Antwoord 6

Dus ik weet niet of dit echt iemand zou helpen, het loste mijn probleem op.
Ook al had ik de OnSharedPreferenceChangeListenergeïmplementeerd zoals aangegeven door het geaccepteerde antwoord. Toch had ik een inconsistentie met de luisteraar die werd gebeld.

Ik kwam hier om te begrijpen dat Android het na enige tijd gewoon opstuurt voor het ophalen van afval. Dus ik keek naar mijn code.
Tot mijn schande had ik de luisteraar niet GLOBALLYverklaard, maar in plaats daarvan in de onCreateView. En dat was omdat ik luisterde naar de Android Studio die me vertelde de luisteraar naar een lokale variabele te converteren.


Antwoord 7

Het is logisch dat de luisteraars in WeakHashMap worden bewaard. Omdat ontwikkelaars de code meestal zo schrijven.

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

Dit lijkt misschien niet slecht. Maar als de container van OnSharedPreferenceChangeListeners niet WeakHashMap was, zou het erg slecht zijn.Als de bovenstaande code in een Activity . Omdat u een niet-statische (anonieme) innerlijke klasse gebruikt die impliciet de referentie van de omsluitende instantie bevat. Dit veroorzaakt een geheugenlek.

Bovendien, als u de listener als een veld houdt, kunt u aan het begin registerOnSharedPreferenceChangeListenergebruiken en aan het einde unregisterOnSharedPreferenceChangeListeneraanroepen. Maar je hebt geen toegang tot een lokale variabele in een methode die buiten het bereik valt. Je hebt dus alleen de mogelijkheid om je aan te melden, maar geen kans om de luisteraar uit te schrijven. Dus het gebruik van WeakHashMap zal het probleem oplossen. Dit is de manier die ik aanbeveel.

Als u de listener-instantie als een statisch veld maakt, wordt het geheugenlek vermeden dat wordt veroorzaakt door een niet-statische interne klasse. Maar aangezien de luisteraars meerdere kunnen zijn, moet het instantie-gerelateerd zijn. Dit verlaagt de kosten voor het afhandelen van de onSharedPreferenceChangedcallback.


Antwoord 8

Tijdens het lezen van voor Word leesbare gegevens gedeeld door de eerste app, moeten we

Vervangen

getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);

met

getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);

in tweede app om bijgewerkte waarde in tweede app te krijgen.

Maar het werkt nog steeds niet…

Other episodes