SharedPreferences.onSharedPreferenceChangeListener не вызывается последовательно
Я регистрирую слушателя изменения предпочтений, как это (в onCreate()
моей основной деятельности):
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.registerOnSharedPreferenceChangeListener(
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(
SharedPreferences prefs, String key) {
System.out.println(key);
}
});
Беда в том, что слушателя не всегда зовут. Первые несколько раз он работает, когда изменяется предпочтение, и затем он больше не вызывается, пока я не удалю и не переустановлю приложение. Никакое количество перезапуска приложения, кажется, не может это исправить.
Я нашел ветку списка рассылки, сообщающую о той же проблеме, но никто не ответил ему. Что я делаю неправильно?
8 ответов
Это подлый. 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 были обновлены с предупреждениями об этом поведении. Таким образом, странное поведение остается. Но теперь это задокументировано.
Этот принятый ответ в порядке, так как для меня он создает новый экземпляр каждый раз, когда действие возобновляется
так как насчет сохранения ссылки на слушателя в деятельности
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);
}
это будет очень похоже на то, что вы делаете, за исключением того, что мы поддерживаем жесткую ссылку.
Принятый ответ создает 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);
}
}
Поскольку это самая подробная страница по теме, я хочу добавить свои 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 из основного Activity:
@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, иначе обработчик в основной операции не будет вызван!!! Мне понадобилось немного времени, чтобы понять, что...
Код Kotlin для регистра SharedPreferenceChangeListener он определяет, когда произойдет изменение сохраненного ключа:
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener { sharedPreferences, key ->
if(key=="language") {
//Do Something
}
}
Вы можете поместить этот код в onStart() или где-то еще.. * Учтите, что вы должны использовать
if(key=="YourKey")
или ваши коды в блоке //Do Something будут выполняться неправильно для каждого изменения, которое произойдет в любом другом ключе в sharedPreferences
Так что я не знаю, поможет ли это кому-нибудь, хотя это решило мою проблему. Хотя я реализовал OnSharedPreferenceChangeListener
как указано в принятом ответе. Тем не менее, у меня было несоответствие с вызываемым слушателем.
Я пришел сюда, чтобы понять, что Android через некоторое время просто отправляет его на сборку мусора. Итак, я посмотрел на мой код. К своему стыду, я не объявил слушателя ГЛОБАЛЬНО, а вместо этого внутри onCreateView
, И это потому, что я слушал Android Studio, в которой мне предлагалось преобразовать слушателя в локальную переменную.
Имеет смысл, что слушатели хранятся в 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.
При чтении удобочитаемых данных Word, передаваемых первым приложением, мы должны
замещать
getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);
с
getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);
во втором приложении, чтобы получить обновленное значение во втором приложении.
Но все равно это не работает...