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

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

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