SharedPreferences.onSharedPreferenceChangeListener sürekli olarak çağrılmıyor


267

Böyle bir tercih değişikliği dinleyicisini kaydediyorum ( onCreate()ana etkinliğimde):

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

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

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

Sorun şu ki, dinleyici her zaman çağrılmaz. Bir tercihin değiştiği ilk birkaç kez çalışır ve uygulamayı kaldırıp yeniden yükleyene kadar artık çağrılmaz. Uygulamanın yeniden başlatılmasının hiçbir miktarı düzeltilemez.

Aynı sorunu bildiren bir posta listesi iş parçacığı buldum , ancak kimse ona cevap vermedi. Neyi yanlış yapıyorum?

Yanıtlar:


612

Bu sinsi bir şey. SharedPreferences dinleyicileri WeakHashMap içinde tutar. Bu, geçerli kapsamdan ayrılır ayrılmaz çöp toplama hedefi olacağından, anonim bir iç sınıfı dinleyici olarak kullanamayacağınız anlamına gelir. İlk başta çalışır, ancak sonunda çöp toplanır, WeakHashMap'ten kaldırılır ve çalışmayı durdurur.

Sınıfınızın bir alanındaki dinleyiciye bir referans tutun ve sınıf örneğiniz yok edilmediği sürece, iyi olacaksınız.

yani:

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

Bunu yap:

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

OnDestroy yönteminde kayıt olmamanın sorunu çözmesinin nedeni, dinleyiciyi bir alanda kaydetmeniz, böylece sorunu önlemenizdir. Dinleyiciyi onDestroy'daki kayıtsızlık değil, sorunu çözen bir alana kaydetmek.

GÜNCELLEME : Android belgeler edilmiştir güncellenen ile uyarılar bu davranış hakkında. Yani, tuhaf davranış devam ediyor. Ama şimdi belgeleniyor.


20
Bu beni öldürüyordu, aklımı kaybettiğimi sanıyordum. Bu çözümü gönderdiğiniz için teşekkür ederiz!
Brad Hein

10
Bu yazı çok büyük, çok teşekkürler, bu bana imkansız hata ayıklama saat mal olabilir!
Kevin Gaudin

Harika, tam da ihtiyacım olan bu. Çok iyi bir açıklama!
stealthcopter

Bu zaten beni ısırdı ve bu yazıyı okuyana kadar neler olduğunu bilmiyordum. Teşekkür ederim! Boo, Android!
Nakedible

5
Harika cevap, teşekkür ederim. Belgelerde kesinlikle belirtilmelidir. code.google.com/p/android/issues/detail?id=48589
Andrey Chernih

16

kabul edilen bu cevap tamam, çünkü benim için etkinlik her devam ettiğinde yeni bir örnek oluşturuyor

dinleyiciye referansı etkinlik içinde tutmaya ne dersiniz?

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

ve onResume ve onPause öğelerinizde

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



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

}

bu, sıkı bir referansta bulunmamız dışında yaptığınız şeye çok benzeyecektir.


Neden daha super.onResume()önce kullandın getPreferenceScreen()...?
Yousha Aleayoub

@YoushaAleayoub, android uygulaması tarafından gerekli android.app.supernotcalledexception hakkında okuyun .
Samuel

ne demek istiyorsun? kullanmak super.onResume()gerekli VEYA kullanmak ÖNCE getPreferenceScreen()gerekli mi? çünkü SAĞ yer hakkında konuşuyorum. cs.dartmouth.edu/~campbell/cs65/lecture05/lecture05.html
Yousha Aleayoub

Burada okuduğumu hatırlıyorum developer.android.com/training/basics/activity-lifecycle/… , koddaki açıklamaya bakın. ama kuyruğa koymak mantıklı. ama şimdiye kadar bu konuda herhangi bir sorunla karşılaşmadım.
Samuel

Çok teşekkürler, başka her yerde ben onResume () ve OnPause () onlar kayıtlı yöntemini bulduğu thisdeğil listener, bu hataya neden ve benim sorunu çözmek olabilir. Bu iki yöntem artık halka açık, korunmuyor
Nicolas

16

Bu konu için en ayrıntılı sayfa olduğu için 50ct eklemek istiyorum.

OnSharedPreferenceChangeListener çağrılmadı sorun vardı. Paylaşılan Tercihlerim ana Etkinliğin başlangıcında şu şekilde alınır:

prefs = PreferenceManager.getDefaultSharedPreferences(this);

My PreferenceActivity kodu kısadır ve tercihleri ​​göstermek dışında hiçbir şey yapmaz:

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

Menü düğmesine her basıldığında ana Etkinlik'ten PreferenceActivity'yi oluştururum:

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

OnSharedPreferenceChangeListener'ı kaydetmenin, bu durumda PreferenceActivity'yi oluşturduktan SONRA yapılması gerektiğini unutmayın , aksi takdirde ana Faaliyetteki İşleyici çağrılmaz !!! Bunu fark etmem biraz zaman aldı ...


9

Kabul edilen cevap SharedPreferenceChangeListenerher onResumeçağrıldığında oluşturulur. @Samuel bunu SharedPreferenceListenerActivity sınıfının bir üyesi yaparak çözer . Ancak Google'ın bu kod etiketinde de kullandığı üçüncü ve daha basit bir çözüm var . Etkinlik sınıfınızın OnSharedPreferenceChangeListenerarabirimi uygulamasını ve onSharedPreferenceChangedEtkinliği geçersiz kılmasını sağlayarak Etkinliğin kendisini etkin kılar a 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
aynen öyle olmalı. arabirimi uygulayın, onStart'a kaydolun ve onStop'a kaydolun.
Junaed

2

Kotlin Kayıtlı anahtarda ne zaman değişiklik olacağını algılayan SharedPreferenceChangeListener kaydı kodu:

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

bu kodu onStart () veya başka bir yere koyabilirsiniz .. * Kullanmanız gerektiğini düşünün

 if(key=="YourKey")

veya "// Bir Şey Yap" bloğundaki kodlarınız, sharedPreferences içindeki başka herhangi bir anahtarda gerçekleşecek her değişiklik için yanlış çalıştırılır


1

Yani, bunun gerçekten kimseye yardım edip etmeyeceğini bilmiyorum, sorunumu çözdü. Ben uygulamış olmama rağmenOnSharedPreferenceChangeListenerKabul edilen cevapta belirtilen şekliyle . Yine de dinleyicinin çağrılmasında bir tutarsızlık yaşadım.

Buraya Android'in bir süre sonra çöp toplama için gönderdiğini anlamaya geldim. Bu yüzden koduma baktım. Utanç için, dinleyiciyi KÜRESEL olarak değil, içinde onCreateView. Bunun nedeni, Android Studio'yu dinleyerek bana dinleyiciyi yerel bir değişkene dönüştürmemizi söyledi.


0

Dinleyicilerin WeakHashMap'te tutulması mantıklıdır, çünkü çoğu zaman geliştiriciler kodu böyle yazmayı tercih ederler.

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

Bu kötü görünmeyebilir. Ancak OnSharedPreferenceChangeListeners'ın kapsayıcısı WeakHashMap olmasaydı, çok kötü olurdu. Yukarıdaki kod bir Aktivitede yazıldıysa. Statik olmayan (anonim) bir iç sınıf kullandığınızdan, bu, kapalı örneğin referansını dolaylı olarak tutacaktır. Bu bellek sızıntısına neden olur.

Dahası, dinleyiciyi alan olarak tutarsanız , başlangıçta registerOnSharedPreferenceChangeListener'ı kullanabilir ve unregisterOnSharedPreferenceChangeListener'ı arayabilirsiniz. ve sonunda . Ancak kapsam dışında bir yöntemde yerel bir değişkene erişemezsiniz. Yani sadece kayıt olma şansınız var ama dinleyicinin kaydını silme şansınız yok. Böylece WeakHashMap kullanmak sorunu çözecektir. Bu şekilde tavsiye ederim.

Dinleyici örneğini statik bir alan olarak yaparsanız, statik olmayan iç sınıfın neden olduğu bellek sızıntısını önler. Ancak dinleyiciler birden fazla olabileceğinden, örnekle ilgili olmalıdır. Bu, onSharedPreferenceChanged geri aramasının işleme maliyetini azaltır .


-3

İlk uygulama tarafından paylaşılan Word tarafından okunabilir verileri okurken,

değiştirmek

getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);

ile

getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);

ikinci app ikinci app güncellenmiş değeri almak için.

Ama yine de çalışmıyor ...


Android, birden çok işlemden SharedPreferences'a erişmeyi desteklemez. Bunu yapmak eşzamanlılık sorunlarına neden olur ve bu da tüm tercihlerin kaybolmasına neden olabilir. Ayrıca, MODE_MULTI_PROCESS artık desteklenmemektedir.
Sam

@Sam bu cevap 3 yaşında, lütfen android'in son sürümlerinde sizin için çalışmıyorsa, aşağı oy vermeyin. cevap yazıldığında bunu yapmak için en iyi yaklaşım buydu.
shridutt kothari

1
Hayır, bu yaklaşımı yazdığınızda bile, bu yaklaşım asla çoklu işlem güvenli değildi.
Sam

@Sam'ın belirttiği gibi, paylaşılan tercihler asla güvenli bir şekilde işlem görmedi. Ayrıca shridutt kothari için - downvotes beğenmezseniz yanlış cevabınızı kaldırın (OP'nin sorusunu yine de cevaplamıyor). Ancak, yine de paylaşılan tercihleri ​​işlem güvenli bir şekilde kullanmak istiyorsanız, üstünde işlem güvenli bir soyutlama oluşturmanız gerekir, yani işlem güvenli olan ve yine de paylaşılan tercihleri ​​kalıcı depolama mekanizması olarak kullanmanızı sağlayan bir ContentProvider. , Bunu daha önce yaptım ve küçük veri kümeleri / tercihler için sqlite'i adil bir farkla gerçekleştirir.
Mark Keen

1
@MarkKeen Bu arada, aslında bunun için içerik sağlayıcıları kullanmayı denedim ve içerik sağlayıcıları Android hataları nedeniyle Üretimde rastgele başarısız olma eğilimindeydi, bu yüzden bu günlerde yapılandırmayı yalnızca ikincil işlemlerle gerektiği gibi senkronize etmek için kullanıyorum.
Sam
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.