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

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

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

Другие вопросы по тегам