SharedPreferences.onSharedPreferenceChangeListener не вызывается последовательно - PullRequest
245 голосов
/ 30 марта 2010

Я регистрирую слушателя изменения предпочтений следующим образом (в onCreate() моей основной деятельности):

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

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

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

Проблема в том, что слушателя не всегда вызывают. Он работает первые несколько раз, когда изменяется предпочтение, и затем он больше не вызывается, пока я не удалю и не переустановлю приложение. Кажется, что никакая перезапуск приложения не может это исправить.

Я нашел список рассылки Тема , сообщающая о той же проблеме, но никто ему не ответил. Что я делаю не так?

Ответы [ 8 ]

571 голосов
/ 23 июня 2010

Это подлый. SharedPreferences держит слушателей в WeakHashMap. Это означает, что вы не можете использовать анонимный внутренний класс в качестве прослушивателя, так как он станет целью сборки мусора, как только вы покинете текущую область. Сначала он будет работать, но, в конце концов, соберет мусор, удалится из 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 устраняет проблему, заключается в том, что для этого необходимо было сохранить прослушиватель в поле, что предотвратило проблему. Устраняет проблему сохранение слушателя в поле, а не отмена регистрации в onDestroy.

ОБНОВЛЕНИЕ : Документы Android были обновлены с предупреждениями об этом поведении. Таким образом, странное поведение остается. Но теперь это задокументировано.

15 голосов
/ 29 декабря 2011

Поскольку это самая подробная страница по теме, я хочу добавить свои 50 кар.

У меня была проблема с тем, что OnSharedPreferenceChangeListener не был вызван. Мои SharedPreferences извлекаются в начале основного действия по:

prefs = PreferenceManager.getDefaultSharedPreferences(this);

Мой код PreferenceActivity короток и ничего не делает, кроме отображения настроек:

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

Каждый раз, когда нажимается кнопка меню, я создаю PreferenceActivity из основного действия:

@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 ПОСЛЕ создания в этом случае PreferenceActivity, иначе не будет вызван обработчик в основной операции !!! Мне понадобилось немного времени, чтобы понять, что ...

13 голосов
/ 25 августа 2011

этот принятый ответ в порядке, так как для меня он создает новый экземпляр каждый раз, когда действие возобновляется

так как насчет сохранения ссылки на слушателя внутри действия

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

}

это будет очень похоже на то, что вы делаете, за исключением того, что мы сохраняем жесткую ссылку.

3 голосов
/ 24 июня 2018

Принятый ответ создает SharedPreferenceChangeListener каждый раз, когда вызывается onResume (). @Samuel решает эту проблему, делая SharedPreferenceListener членом класса Activity. Но есть третье и более простое решение, которое 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 голос
/ 21 августа 2018

Итак, я не знаю, поможет ли это кому-нибудь, хотя, это решило мою проблему. Хотя я реализовал OnSharedPreferenceChangeListener, как указано в принятом ответе . Тем не менее, у меня было несоответствие с вызываемым слушателем.

Я пришел сюда, чтобы понять, что Android через некоторое время просто отправляет его на сборку мусора. Итак, я посмотрел на мой код. К своему стыду, я не объявил слушателя GLOBALLY , а вместо этого внутри onCreateView. И это потому, что я слушал Android Studio, в которой мне предлагалось преобразовать слушателя в локальную переменную.

0 голосов
/ 14 января 2019

Код Котлина для регистра SharedPreferenceChangeListener, который он определяет, когда произойдет изменение сохраненного ключа:

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

вы можете поместить этот код в onStart () или где-то еще .. * Считайте, что вы должны использовать

 if(key=="YourKey")

или ваши коды в блоке // Do Something будут выполняться неправильно для каждого изменения, которое будет происходить с любым другим ключом в sharedPreferences

0 голосов
/ 27 ноября 2014

Имеет смысл, что слушатели хранятся в WeakHashMap. Поскольку в большинстве случаев разработчики предпочитают писать код, подобный этому.

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

Это может показаться неплохим. Но если бы контейнер OnSharedPreferenceChangeListeners не был WeakHashMap, это было бы очень плохо. Если код выше был написан в Activity. Поскольку вы используете нестатический (анонимный) внутренний класс, который неявно содержит ссылку на включающий экземпляр. Это приведет к утечке памяти.

Более того, если вы сохраняете слушателя как поле, вы можете использовать registerOnSharedPreferenceChangeListener в начале и вызывать unregisterOnSharedPreferenceChangeListener в конце. Но вы не можете получить доступ к локальной переменной в методе, находящемся за ее пределами. Таким образом, у вас есть возможность зарегистрироваться, но нет возможности отменить регистрацию слушателя. Таким образом, использование WeakHashMap решит проблему. Именно так я рекомендую.

Если вы сделаете экземпляр слушателя статическим полем, это позволит избежать утечки памяти, вызванной нестатическим внутренним классом. Но так как слушателей может быть несколько, это должно быть связано с экземпляром. Это уменьшит стоимость обработки обратного вызова onSharedPreferenceChanged .

0 голосов
/ 11 февраля 2013

При чтении читаемых данных Word, передаваемых первому приложению, мы должны

заменить

getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);

на

getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);

во втором приложении, чтобы получить обновленное значение в секундуприложение.

Но все равно не работает ...

...