我正在注册一个这样的首选项更改侦听器(在我的主活动的onCreate()中):

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

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

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

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

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


当前回答

监听器保存在WeakHashMap中是有意义的。因为大多数时候,开发人员更喜欢这样编写代码。

PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).registerOnSharedPreferenceChangeListener(
    new OnSharedPreferenceChangeListener() {
    @Override
    public void onSharedPreferenceChanged(
        SharedPreferences sharedPreferences, String key) {
        Log.i(LOGTAG, "testOnSharedPreferenceChangedWrong key =" + key);
    }
});

这似乎并不坏。但是如果onsharedpreferencechangelistener的容器不是WeakHashMap,那就很糟糕了。如果上面的代码是在活动中编写的。由于您使用的是非静态(匿名)内部类,它将隐式地保存封闭实例的引用。这将导致内存泄漏。

更重要的是,如果你保持监听器作为一个字段,你可以在开始时使用registerOnSharedPreferenceChangeListener,并在结束时调用unregisterOnSharedPreferenceChangeListener。但是您不能在方法的作用域之外访问局部变量。所以你只有注册的机会,没有取消注册的机会。因此,使用WeakHashMap将解决这个问题。这是我推荐的方法。

如果将侦听器实例设置为静态字段,则可以避免非静态内部类造成的内存泄漏。但是由于侦听器可以是多个,所以它应该是与实例相关的。这将减少处理onSharedPreferenceChanged回调的成本。

其他回答

监听器保存在WeakHashMap中是有意义的。因为大多数时候,开发人员更喜欢这样编写代码。

PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).registerOnSharedPreferenceChangeListener(
    new OnSharedPreferenceChangeListener() {
    @Override
    public void onSharedPreferenceChanged(
        SharedPreferences sharedPreferences, String key) {
        Log.i(LOGTAG, "testOnSharedPreferenceChangedWrong key =" + key);
    }
});

这似乎并不坏。但是如果onsharedpreferencechangelistener的容器不是WeakHashMap,那就很糟糕了。如果上面的代码是在活动中编写的。由于您使用的是非静态(匿名)内部类,它将隐式地保存封闭实例的引用。这将导致内存泄漏。

更重要的是,如果你保持监听器作为一个字段,你可以在开始时使用registerOnSharedPreferenceChangeListener,并在结束时调用unregisterOnSharedPreferenceChangeListener。但是您不能在方法的作用域之外访问局部变量。所以你只有注册的机会,没有取消注册的机会。因此,使用WeakHashMap将解决这个问题。这是我推荐的方法。

如果将侦听器实例设置为静态字段,则可以避免非静态内部类造成的内存泄漏。但是由于侦听器可以是多个,所以它应该是与实例相关的。这将减少处理onSharedPreferenceChanged回调的成本。

所以,我不知道这是否真的能帮助到任何人,但它解决了我的问题。 尽管我已经实现了OnSharedPreferenceChangeListener,因为接受的答案。尽管如此,我还是与被呼叫的听众有矛盾。

我来这里是为了了解Android只是在一段时间后才将其发送给垃圾收集。所以,我检查了一下我的代码。 遗憾的是,我没有全局声明监听器,而是在onCreateView中声明监听器。那是因为我听了Android Studio告诉我把监听器转换成一个局部变量。

这是一个狡猾的例子。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文档已经更新了关于这种行为的警告。所以,奇怪的行为仍然存在。但现在它被记录下来了。

Kotlin代码,用于注册SharedPreferenceChangeListener,它检测何时将发生在保存的键上的变化:

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

你可以把这个代码放在onStart(),或其他地方.. *考虑你必须使用

 if(key=="YourKey")

或者你的代码在“//Do Something”块将错误地运行每一个变化,将发生在任何其他键在sharedPreferences

这个公认的答案是好的,对我来说,它是创建新的实例,每次活动恢复

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

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

}

这将非常类似于你所做的,除了我们保持一个硬参考。