284

我正在注册一个这样的偏好更改侦听器(在onCreate()我的主要活动中):

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

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

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

问题是,侦听器并不总是被调用。它适用于首选项更改的前几次,然后在我卸载并重新安装应用程序之前不再调用它。重新启动应用程序似乎无法修复它。

我发现一个邮件列表线程报告了同样的问题,但没有人真正回答他。我究竟做错了什么?

4

8 回答 8

662

这是一个偷偷摸摸的。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 文档已更新,包含有关此行​​为的警告。所以,古怪的行为仍然存在。但现在它已记录在案。

于 2010-06-23T18:02:50.780 回答
17

这个接受的答案没问题,对我来说,每次活动恢复时它都会创建新实例

那么如何在活动中保持对侦听器的引用

OnSharedPreferenceChangeListener listener = new OnSharedPreferenceChangeListener(){
      public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
         // your stuff
      }
};

在你的 onResume 和 onPause

@Override     
public void onResume() {
    super.onResume();          
    getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);     
}

@Override     
public void onPause() {         
    super.onPause();          
    getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);

}

这将与您正在做的事情非常相似,只是我们维护的是硬参考。

于 2011-08-25T04:45:07.257 回答
16

由于这是该主题的最详细页面,因此我想添加 50 克拉。

我遇到了未调用 OnSharedPreferenceChangeListener 的问题。我的 SharedPreferences 在主 Activity 开始时通过以下方式检索:

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

每次按下菜单按钮时,我都会从主 Activity 创建 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 之后完成,否则主 Activity 中的 Handler 将不会被调用!!!我花了一些甜蜜的时间才意识到……

于 2011-12-29T12:42:40.633 回答
16

SharedPreferenceChangeListener接受的答案每次onResume调用都会创建一个。@Samuel通过成为SharedPreferenceListenerActivity 类的成员来解决它。但是谷歌还在这个 codelab中使用了第三种也是更直接的解决方案。让你的 Activity 类在 Activity 中实现OnSharedPreferenceChangeListener接口和覆盖onSharedPreferenceChanged,有效地使 Activity 本身成为一个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);
    }
}
于 2018-06-24T17:05:58.587 回答
2

用于注册 SharedPreferenceChangeListener 的 Kotlin 代码它检测保存的密钥何时发生更改:

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

您可以将此代码放在 onStart() 或其他地方.. *考虑您必须使用

 if(key=="YourKey")

或者您在“//Do Something”块中的代码对于 sharedPreferences 中任何其他键中发生的每个更改都会错误地运行

于 2019-01-14T19:16:02.570 回答
1

所以,我不知道这是否真的对任何人有帮助,它解决了我的问题。即使我已经OnSharedPreferenceChangeListener按照接受的答案实施了。尽管如此,我还是与被呼叫的听众不一致。

我来到这里是为了了解 Android 只是在一段时间后将其发送给垃圾收集。所以,我查看了我的代码。令我感到羞耻的是,我没有在GLOBALLY中声明监听器,而是在onCreateView. 那是因为我听了 Android Studio 告诉我将侦听器转换为局部变量。

于 2018-08-21T01:09:25.563 回答
0

将侦听器保存在 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。但是您不能在方法中访问其范围之外的局部变量。所以你只有注册的机会,但没有机会注销监听器。因此使用 Wea​​kHashMap 将解决问题。这是我推荐的方式。

如果将监听器实例设为静态字段,将避免非静态内部类导致的内存泄漏。但是由于监听器可能是多个,它应该是实例相关的。这将降低处理onSharedPreferenceChanged回调的成本。

于 2014-11-27T12:10:23.780 回答
-3

在读取第一个应用程序共享的 Word 可读数据时,我们应该

代替

getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);

getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);

在第二个应用程序中获取第二个应用程序中的更新值。

但它仍然无法正常工作......

于 2013-02-11T19:00:03.887 回答