web-dev-qa-db-ja.com

SharedPreferences.onSharedPreferenceChangeListenerが一貫して呼び出されない

このような設定変更リスナーを登録しています(メインアクティビティのonCreate()内):

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

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

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

問題は、リスナーが常に呼び出されるとは限らないことです。設定が変更された最初の数回は機能し、アプリをアンインストールして再インストールするまで呼び出されなくなります。アプリケーションを再起動しても問題は解決しないようです。

私はメーリングリストを見つけました thread 同じ問題を報告していますが、誰も彼に本当に答えませんでした。私は何を間違えていますか?

248
synic

これは卑劣なものです。 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で登録解除するのではなく、問題を修正するフィールドにリスナーを保存することです。

UPDATE:Androidドキュメントは pdated with warnings この動作について。したがって、オッドボールの動作は残ります。しかし、今では文書化されています。

578
Blanka

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

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を作成します。

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

この場合、PreferenceActivityを作成した後にOnSharedPreferenceChangeListenerを登録する必要があります。そうしないと、メインアクティビティのHandlerが呼び出されません!!!それを実現するために私はいくつかの甘い時間がかかりました...

15
Bim

この受け入れられた答えは大丈夫です、私にとっては新しいインスタンスアクティビティが再開するたびに作成しています

アクティビティ内でリスナーへの参照を維持するのはどうですか

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

}

これは、ハードリファレンスを維持していることを除いて、あなたがやっていることと非常に似ています。

14
Samuel

受け入れられた答えは、SharedPreferenceChangeListenerが呼び出されるたびにonResumeを作成します。 @ Samuelは、SharedPreferenceListenerをActivityクラスのメンバーにすることで解決します。しかし、Googlethis codelab でも使用する3番目のより簡単なソリューションがあります。 。アクティビティクラスに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);
    }
}
5
PrashanD

だから、これが本当に誰にも役立つかどうかはわかりませんが、それは私の問題を解決しました。 受け入れられた答えで述べられているようにOnSharedPreferenceChangeListenerを実装しましたが。それでも、呼び出されているリスナーと矛盾がありました。

Androidがしばらくしてガベージコレクションのために送信することを理解するためにここに来ました。それで、私は自分のコードを見ました。残念なことに、リスナーを宣言していませんでしたグローバルに代わりにonCreateView内で。それは、Android St​​udioを聞いて、リスナーをローカル変数に変換するように指示したためです。

1
Asghar Musani

保存されたキーに変更が発生するタイミングを検出するSharedPreferenceChangeListenerを登録するためのKotlinコード:

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

このコードはonStart()または他の場所に配置できます。*使用する必要があることを考慮してください

 if(key=="YourKey")

または、// Do Somethingブロックのコードは、sharedPreferencesの他のキーで発生するすべての変更に対して誤って実行されます

0
Hamed Jaliliani

リスナーが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を使用し、最後にnregisterOnSharedPreferenceChangeListenerを使用できます。しかし、メソッドのスコープ外のローカル変数にアクセスすることはできません。したがって、登録する機会はありますが、リスナーを登録解除する機会はありません。したがって、WeakHashMapを使用すると問題が解決します。これが私が推奨する方法です。

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

0
androidyue