SharedPreferences.onSharedPreferenceChangeListener non essere chiamato in modo coerente

StackOverflow https://stackoverflow.com/questions/2542938

  •  23-09-2019
  •  | 
  •  

Domanda

Sto registrando una modifica delle preferenze di ascoltatore come questo (in onCreate() la mia attività principale):

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

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

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

Il problema è che l'ascoltatore non viene sempre chiamato.Funziona per le prime volte una preferenza è cambiato, e quindi non è più chiamato fino a disinstallare e reinstallare l'app.Nessuna quantità di riavviare l'applicazione sembra risolvere il problema.

Ho trovato una mailing list thread reporting stesso problema, ma in realtà nessuno gli rispose.Che cosa sto facendo di sbagliato?

È stato utile?

Soluzione

Questo è un subdolo. SharedPreferences mantiene gli ascoltatori in un WeakHashMap. Ciò significa che non è possibile utilizzare una classe interna anonima come ascoltatore, come diventerà il bersaglio di raccolta dei rifiuti, non appena si lascia l'ambito corrente. Si lavorerà in un primo momento, ma alla fine, otterrà garbage collection, rimosso dal WeakHashMap e smettere di lavorare.

Mantenere un riferimento per l'ascoltatore in un campo della classe e sarete OK, a condizione che l'istanza della classe non viene distrutta.

vale a dire. invece di:

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

fare questo:

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

Il motivo l'annullamento della registrazione nel metodo OnDestroy risolve il problema è dovuto al fatto di fare che si doveva salvare l'ascoltatore in un campo, impedendo quindi il problema. E 'il risparmio l'ascoltatore in un campo che consente di risolvere il problema, non la l'annullamento della registrazione in OnDestroy.

Aggiorna : La documentazione di Android sono stati aggiornato con avvertimenti su questo comportamento. Così, il comportamento stravagante rimane. Ma ora è documentato.

Altri suggerimenti

Questo è il più dettagliata pagina del topic, vorrei aggiungere la mia 50ct.

Ho avuto il problema che OnSharedPreferenceChangeListener non è stato chiamato.Il mio SharedPreferences vengono recuperati all'inizio delle Attività da:

prefs = PreferenceManager.getDefaultSharedPreferences(this);

Il mio PreferenceActivity codice è breve e non fa nulla, tranne che mostra le preferenze:

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

Ogni volta che viene premuto il tasto menu ho creato il PreferenceActivity dalle principali Attività:

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

Nota che la registrazione del OnSharedPreferenceChangeListener deve essere fatto DOPO la creazione del PreferenceActivity in questo caso, altrimenti il Gestore nell'Attività principale non verrà più chiamato!!!Mi ci è voluto un po ' di dolce per rendersi conto che...

questa risposta accettata è ok, come per me è la creazione di nuova istanza ogni volta che l'attività riprende

così come di mantenere il riferimento al listener nell'ambito dell'attività

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

e nella vostra onResume e onPause

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



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

}

questo sarà molto simile a quello che si sta facendo tranne stiamo mantenendo un riferimento concreto.

La risposta accettata crea un SharedPreferenceChangeListener ogni volta onResume è chiamato. @Samuel risolve facendo SharedPreferenceListener un membro della classe di attività. Ma c'è una terza e una soluzione più semplice che Google utilizza anche in questo codelab . Fai la tua classe di attività implementi l'interfaccia OnSharedPreferenceChangeListener e override onSharedPreferenceChanged in attività, di fatto rendendo l'attività stessa 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);
    }
}

Così, non so se questo sarebbe davvero di aiuto a nessuno, però, ha risolto il mio problema.Anche se io avevo realizzato il OnSharedPreferenceChangeListener come ha dichiarato il accettato risposta.Ancora, ho avuto un problema di incoerenza con l'ascoltatore di essere chiamato.

Sono venuto qui per capire che Android appena lo invia per la raccolta dei rifiuti dopo qualche tempo.Così, ho guardato il mio codice.Per mia vergogna, non avevo dichiarato l'ascoltatore A livello GLOBALE ma invece all'interno del onCreateView.E che è perché ho ascoltato Android Studio mi dice di convertire l'ascoltatore in una variabile locale.

ha senso che gli ascoltatori sono tenuti in WeakHashMap.Because maggior parte del tempo, gli sviluppatori preferiscono scrivere il codice come questo.

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

Questo può sembrare non è male. Ma se il contenitore dei OnSharedPreferenceChangeListeners' non era WeakHashMap, sarebbe molto bad.If il codice di cui sopra è stato scritto in un'attività. Dal momento che si sta utilizzando non statici (anonimo) classe interna che sarà detiene implicitamente il riferimento dell'istanza racchiude. Ciò causerà perdita di memoria.

Cosa c'è di più, se si mantiene l'ascoltatore come un campo, è possibile utilizzare registerOnSharedPreferenceChangeListener in partenza e chiamare unregisterOnSharedPreferenceChangeListener alla fine. Ma non si può accedere a una variabile locale in un metodo fuori di esso di ambito. Quindi, non resta che la possibilità di registrarsi, ma nessuna possibilità di annullare la registrazione l'ascoltatore. Così utilizzando WeakHashMap risolverà il problema. Questo è il modo in cui mi raccomando.

Se si effettua l'istanza ascoltatore come un campo statico, eviterà la perdita di memoria causata dalla classe interna non statica. Ma, come gli ascoltatori potrebbero essere molteplici, dovrebbe essere esempio-correlate. Ciò consentirà di ridurre i costi di gestione della onSharedPreferenceChanged di callback.

Codice Kotlin per il registro SharedPreferenceChangeListener si rileva quando il cambiamento sarà accadendo sulla chiave salvata:

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

è possibile inserire questo codice in onStart (), o da qualche altra parte .. * Si consideri che è necessario utilizzare

 if(key=="YourKey")

oi vostri codici in // fare qualcosa blocco verrà eseguito erroneamente per ogni cambiamento che avviene in qualsiasi altro tasto sharedPreferences

Durante la lettura di parole dati leggibili condivisi da prima applicazione, dovremmo

Sostituire

getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);

con

getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);

in seconda applicazione per ottenere valore aggiornato in seconda app.

Ma ancora non funziona ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top