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

これはトピックの最も詳細なページなので、50ct を追加したいと思います。

OnSharedPreferenceChangeListener が呼び出されないという問題がありました。My 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 の作成後に行う必要があることに注意してください。そうしないと、メイン アクティビティの Handler が呼び出されません!!! それに気付くのに、甘い時間がかかりました...

于 2011-12-29T12:42:40.633 に答える
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を呼び出すことができます。ただし、スコープ外のメソッドでローカル変数にアクセスすることはできません。したがって、登録する機会はありますが、リスナーの登録を解除する機会はありません。したがって、WeakHashMap を使用すると問題が解決します。これが私が推奨する方法です。

リスナーのインスタンスを静的フィールドにすることで、非静的内部クラスによるメモリリークを回避できます。ただし、リスナーは複数になる可能性があるため、インスタンスに関連する必要があります。これにより、 onSharedPreferenceChangedコールバックを処理するコストが削減されます。

于 2014-11-27T12:10:23.780 に答える
-3

最初のアプリで共有された Word で読み取り可能なデータを読み込んでいる間、

交換

getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);

getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);

2 番目のアプリで、2 番目のアプリで更新された値を取得します。

しかし、まだ機能していません...

于 2013-02-11T19:00:03.887 に答える