SharedPreferences.onSharedPreferenceChangeListener未被一致地调用

时间:2010-03-30 04:55:36

标签: android android-preferences

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

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

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

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

麻烦的是,听众并不总是被召唤。它最初几次更改首选项,然后在卸载并重新安装应用程序之前不再调用它。没有多少重启应用程序似乎解决了它。

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

8 个答案:

答案 0 :(得分:569)

这是一个鬼鬼祟祟的人。 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文档updatedwarnings有关此行为。所以,奇怪的行为仍然存在。但现在已经记录在案了。

答案 1 :(得分:15)

由于这是我想添加50ct主题的最详细页面。

我遇到的问题是没有调用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将不会被调用!!!我花了很多时间才意识到......

答案 2 :(得分:13)

这个接受的答案是可以的,因为每次活动恢复时都会创建新实例

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

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 :(得分:3)

每次调用onResume()时,接受的答案都会创建一个SharedPreferenceChangeListener。 @Samuel通过使SharedPreferenceListener成为Activity类的成员来解决它。但是Google在 this codelab 中也使用了第三个更直接的解决方案。使您的活动类实现OnSharedPreferenceChangeListener接口并覆盖Activity中的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);
    }
}

答案 4 :(得分:1)

所以,我不知道这是否真的可以帮助任何人,它解决了我的问题。 即使我已经按照接受的答案所述执行了OnSharedPreferenceChangeListener。不过,我与被调用的监听器之间存在不一致之处。

我来到这里是为了了解Android会在一段时间后将其发送以进行垃圾回收。因此,我查看了我的代码。 令我感到羞耻的是,我没有将监听器声明为 GLOBAL ,而是在onCreateView内声明了。那是因为我听了Android Studio的通知,要求我将监听器转换为本地变量。

答案 5 :(得分: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 。但是你不能从它的范围内访问方法中的局部变量。所以你只是有机会注册但没有机会取消注册听众。因此使用WeakHashMap将解决问题。这是我推荐的方式。

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

答案 6 :(得分:0)

Kotlin寄存器SharedPreferenceChangeListener的代码,它检测何时对已保存的密钥进行更改:

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

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

 if(key=="YourKey")

或您在// Do Something块中的代码将对共享首选项中任何其他键中发生的每次更改错误地运行

答案 7 :(得分:-4)

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

替换

getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);

getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);

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

但它仍然没有用......