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