SharedPreferences.onSaredPreferenceChangeListener को लगातार नहीं बुलाया जा रहा है


267

मैं इस तरह ( onCreate()मेरी मुख्य गतिविधि के लिए) एक वरीयता परिवर्तन श्रोता को पंजीकृत कर रहा हूँ :

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

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

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

मुसीबत यह है कि, श्रोता को हमेशा नहीं बुलाया जाता है। यह पहले कुछ समय के लिए काम करता है एक वरीयता को बदल दिया जाता है, और तब तक इसे कॉल नहीं किया जाता है जब तक कि मैं ऐप को अनइंस्टॉल और पुनर्स्थापित नहीं करता। एप्लिकेशन को पुनरारंभ करने की कोई भी राशि इसे ठीक करने के लिए नहीं लगती है।

मुझे एक मेलिंग सूची धागा समान समस्या की सूचना मिली , लेकिन किसी ने भी वास्तव में उसका जवाब नहीं दिया। मैं क्या गलत कर रहा हूं?

जवाबों:


612

यह एक डरपोक है। SharedPreferences श्रोताओं को एक WeakHashMap में रखता है। इसका मतलब यह है कि आप एक श्रोता के रूप में एक अनाम आंतरिक वर्ग का उपयोग नहीं कर सकते हैं, क्योंकि जैसे ही आप वर्तमान गुंजाइश छोड़ते हैं, यह कचरा संग्रह का लक्ष्य बन जाएगा। यह पहली बार में काम करेगा, लेकिन आखिरकार, कचरा इकट्ठा हो जाएगा, वीकशॉशप से हटा दिया जाएगा और काम करना बंद कर देगा।

अपनी कक्षा के किसी क्षेत्र के श्रोता के लिए एक संदर्भ रखें और आप ठीक होंगे, बशर्ते आपका वर्ग उदाहरण नष्ट न हो।

इसके बजाय अर्थात:

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

यह करो:

// 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 विधि में अपंजीकृत कारण समस्या को ठीक करता है क्योंकि ऐसा करने के लिए आपको श्रोता को एक फ़ील्ड में सहेजना था, इसलिए समस्या को रोकना था। यह श्रोता को एक ऐसे क्षेत्र में बचत कर रहा है जो समस्या को ठीक करता है, न कि ऑनड्रेस्ट्रॉय में अपंजीकृत।

अद्यतन : Android डॉक्स को इस व्यवहार के बारे में चेतावनी के साथ अद्यतन किया गया है। तो, ऑडबॉल व्यवहार रहता है। लेकिन अब यह प्रलेखित है।


20
यह मुझे मार रहा था, मुझे लगा कि मैं अपना दिमाग खो रहा हूं। इस समाधान को पोस्ट करने के लिए धन्यवाद!
ब्रैड हेन सेप

10
यह पोस्ट बहुत बड़ी है, बहुत बहुत धन्यवाद, इससे मुझे असंभव डिबगिंग के कुछ घंटे लग सकते थे!
केविन गौडिन

बहुत बढ़िया, यह वही है जो मुझे चाहिए था। अच्छी व्याख्या भी!
स्टील्कॉप्टर

यह पहले से ही मेरे लिए है, और मुझे नहीं पता था कि इस पोस्ट को पढ़ने तक क्या था। धन्यवाद! बू, Android!
रात्रि 8:30

5
शानदार जवाब, धन्यवाद। डॉक्स में निश्चित रूप से उल्लेख किया जाना चाहिए। code.google.com/p/android/issues/detail?id=48589
एंड्री चेर्निह

16

यह स्वीकार किया गया उत्तर ठीक है, क्योंकि मेरे लिए यह गतिविधि शुरू होने पर हर बार नया उदाहरण बना रहा है

तो गतिविधि के भीतर श्रोता के संदर्भ को कैसे रखा जाए

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

और अपने onResume और onPause में

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



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

}

यह बहुत कुछ वैसा ही है जैसा आप कर रहे हैं सिवाय इसके कि हम एक कठिन संदर्भ बनाए हुए हैं।


आपने super.onResume()पहले क्यों इस्तेमाल किया getPreferenceScreen()...?
युषा अलायूब

@YoushaAleayoub, android.app.supernotcalledexception के बारे में पढ़ें, इसे Android कार्यान्वयन द्वारा आवश्यक है।
शमूएल

आपका मतलब क्या है? उपयोग super.onResume()करना आवश्यक है या इसका उपयोग करना getPreferenceScreen()आवश्यक है? क्योंकि मैं सही जगह के बारे में बात कर रहा हूँ। cs.dartmouth.edu/~campbell/cs65/lecture05/lecture05.html
Yousha Aleayoub

मुझे यहां पढ़ना याद है डेवलपर android.com/training/basics/activity-lifecycle/… , कोड में टिप्पणी देखें। लेकिन इसे पूंछ में रखना तर्कसंगत है। लेकिन अब तक मुझे इसमें कोई समस्या नहीं हुई।
सैमुअल

बहुत बहुत धन्यवाद, हर जगह जो मैंने पाया onResume () और onPause () विधि उन्होंने पंजीकृत किया thisऔर नहीं listener, इससे त्रुटि हुई और मैं अपनी समस्या को हल कर सका। Btw इन दो तरीकों अब सार्वजनिक कर रहे हैं, संरक्षित नहीं
निकोलस

16

जैसा कि इस विषय के लिए यह सबसे विस्तृत पृष्ठ है, जिसे मैं अपना 50ct जोड़ना चाहता हूं।

मुझे समस्या थी कि OnSaringPreferenceChangeListener को नहीं बुलाया गया था। मेरे SharedPreferences मुख्य गतिविधि के प्रारंभ में प्राप्त होते हैं:

prefs = PreferenceManager.getDefaultSharedPreferences(this);

मेरा प्राथमिकता कोड छोटा है और वरीयताओं को दिखाने के अलावा कुछ नहीं करता है:

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

हर बार जब मेन्यू बटन दबाया जाता है तो मैं मुख्य गतिविधि से प्राथमिकता बनाता हूं:

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

ध्यान दें कि OnSaringPreferenceChangeListener को पंजीकृत करने के लिए इस मामले में प्राथमिकता बनाने के बाद काम करने की आवश्यकता है, अन्यथा मुख्य गतिविधि में हैंडलर को नहीं बुलाया जाएगा !!! यह महसूस करने में मुझे कुछ मीठा समय लगा कि ...


9

स्वीकृत उत्तर SharedPreferenceChangeListenerहर बार बनने वाले onResumeको कहा जाता है। @ शमूएल इसे SharedPreferenceListenerगतिविधि वर्ग का सदस्य बनाकर हल करता है । लेकिन एक तीसरा और अधिक सीधा समाधान है जिसका उपयोग Google इस कोडलैब में भी करता है । गतिविधि में OnSharedPreferenceChangeListenerइंटरफ़ेस और ओवरराइड onSharedPreferenceChangedको लागू करने के लिए अपनी गतिविधि कक्षा को प्रभावी ढंग से गतिविधि बनाने के लिए खुद को ए 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
वास्तव में, यह होना चाहिए। इंटरफ़ेस लागू करें, ऑनस्टार्ट में पंजीकरण करें और ऑनटॉप में अनरजिस्टर करें।
13

2

रजिस्टर के लिए कोटलिन कोड साझा किए गए परिवर्तन के माध्यम से यह पता लगाता है कि सहेजे गए कुंजी में परिवर्तन कब होगा:

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

आप इस कोड को onStart (), या कहीं और रख सकते हैं .. * विचार करें कि आपको इसका उपयोग करना चाहिए

 if(key=="YourKey")

या "कुछ करो" ब्लॉक के अंदर के आपके कोड हर बदलाव के लिए गलत तरीके से चलाए जाएंगे, जो कि शेयर्ड संदर्भों में किसी अन्य कुंजी में हो रहे हैं


1

इसलिए, मुझे नहीं पता कि क्या यह वास्तव में किसी की मदद करेगा हालांकि, इसने मेरी समस्या हल कर दी। यद्यपि मैंने स्वीकृत उत्तरOnSharedPreferenceChangeListener द्वारा बताए अनुसार लागू किया था । फिर भी, मुझे सुनने वाले के साथ एक असंगतता थी।

मैं यहां यह समझने के लिए आया हूं कि एंड्रॉइड इसे कुछ समय के बाद कचरा संग्रह के लिए भेजता है। इसलिए, मैंने अपने कोड को देखा। मेरी शर्म की बात है, मैं श्रोता घोषित नहीं किया था विश्व स्तर पर , लेकिन इसके बजाय अंदर onCreateView। और ऐसा इसलिए था क्योंकि मैंने एंड्रॉइड स्टूडियो को सुनने के लिए कहा था कि मैं श्रोता को एक स्थानीय चर में बदल सकता हूं।


0

यह समझ में आता है कि श्रोताओं को WeakHashMap.Because में रखा जाता है, ज्यादातर डेवलपर्स इस तरह कोड लिखना पसंद करते हैं।

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

यह बुरा नहीं लग सकता है। लेकिन अगर OnSaringPreferenceChangeListeners 'कंटेनर WeakHashMap नहीं था, तो यह बहुत बुरा होगा। यदि उपरोक्त कोड एक गतिविधि में लिखा गया था। चूंकि आप गैर-स्थैतिक (अनाम) आंतरिक वर्ग का उपयोग कर रहे हैं, जो संक्षेप में संलग्न उदाहरण का संदर्भ देगा। इससे मेमोरी लीक होगी।

और क्या आप एक क्षेत्र के रूप में श्रोता रखने के हैं, तो आप इस्तेमाल कर सकते हैं है, registerOnSharedPreferenceChangeListener प्रारंभ और कॉल पर unregisterOnSharedPreferenceChangeListener अंत में। लेकिन आप इसके दायरे से बाहर किसी विधि में स्थानीय चर का उपयोग नहीं कर सकते। तो आपके पास केवल पंजीकरण करने का अवसर है लेकिन श्रोता को अपंजीकृत करने का कोई मौका नहीं। इस प्रकार WeakHashMap का उपयोग करने से समस्या का समाधान हो जाएगा। यह वह तरीका है जो मैं सुझाता हूं।

यदि आप श्रोता उदाहरण को एक स्थिर क्षेत्र के रूप में बनाते हैं, तो यह गैर-स्थिर आंतरिक वर्ग के कारण होने वाली मेमोरी लीक से बच जाएगा। लेकिन जैसे-जैसे श्रोता कई हो सकते हैं, यह उदाहरण से संबंधित होना चाहिए। यह onSaringPreferenceChanged कॉलबैक को संभालने की लागत को कम करेगा ।


-3

पहले एप्लिकेशन द्वारा साझा किए गए वर्ड पठनीय डेटा को पढ़ते समय, हमें चाहिए

बदलने के

getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);

साथ में

getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);

दूसरे ऐप में दूसरे ऐप में अपडेट वैल्यू पाने के लिए।

लेकिन फिर भी यह काम नहीं कर रहा है ...


Android कई प्रक्रियाओं से SharedPreferences तक पहुँचने का समर्थन नहीं करता है। ऐसा करने से समवर्ती समस्याएँ उत्पन्न होती हैं, जिसके परिणामस्वरूप सभी प्राथमिकताएँ खो सकती हैं। इसके अलावा, MODE_MULTI_PROCESS अब समर्थित नहीं है।
सैम

@ यह उत्तर 3 साल पुराना है, कृपया इसे डाउन-वोट न करें, यदि इसका आपके लिए Android के नवीनतम संस्करणों में काम नहीं कर रहा है। समय उत्तर लिखा गया था ऐसा करने के लिए यह सबसे अच्छा तरीका था।
श्रीदत्त कोठारी

1
नहीं, यह दृष्टिकोण कभी भी बहु प्रक्रिया सुरक्षित नहीं था, तब भी जब आपने यह उत्तर लिखा था।
सैम

जैसा कि @Sam बताता है, सही ढंग से, साझा किए गए Prefs कभी भी सुरक्षित प्रक्रिया नहीं थे। इसके अलावा श्रीदत्त कोठारी को भी - अगर आपको यह पसंद नहीं है कि आपके गलत उत्तर को हटा दें (यह वैसे भी ओपी के सवाल का जवाब नहीं देता है)। हालाँकि, यदि आप अभी भी प्रक्रिया में साझा प्रीफ़ का उपयोग करना चाहते हैं तो सुरक्षित तरीके से आपको इसके ऊपर एक प्रक्रिया सुरक्षित एब्स्ट्रैक्शन बनाने की आवश्यकता है, अर्थात एक कंटेंटप्रोइडर, जो कि सुरक्षित प्रक्रिया है, और फिर भी आपको निरंतर स्टोरेज तंत्र के रूप में साझा प्राथमिकताओं का उपयोग करने की अनुमति देता है। , मैंने पहले भी ऐसा किया है, और छोटे डेटासेट / वरीयताओं के लिए उचित मार्जिन के द्वारा साइक्लाइट करता है।
मार्क कीन

1
@MarkKeen वैसे, मैंने वास्तव में इसके लिए कंटेंट प्रदाताओं का उपयोग करने की कोशिश की और एंड्रॉइड बग्स के कारण कंटेंट प्रोवाइडर्स बेतरतीब ढंग से फेल हो गए, इसलिए इन दिनों मैं जरूरत के मुताबिक कॉन्फ़िगरेशन को सेकेंडरी प्रोसेस में सिंक करने के लिए ब्रॉडकास्ट का इस्तेमाल करता हूं।
सैम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.