SharedPreferences.onSharedPreferenceChangeListener가 지속적으로 호출되지 않습니다


267

onCreate()기본 활동 에서 다음과 같이 환경 설정 변경 리스너를 등록하고 있습니다 .

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

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

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

문제는 청취자가 항상 호출되는 것은 아닙니다. 환경 설정이 처음 몇 번 변경되면 앱을 제거했다가 다시 설치할 때까지 더 이상 호출되지 않습니다. 응용 프로그램을 다시 시작해도 문제가 해결되지 않는 것 같습니다.

같은 문제를보고 하는 메일 링리스트 스레드를 찾았 지만 아무도 그에게 대답하지 않았습니다. 내가 뭘 잘못하고 있죠?

답변:


612

이것은 비열한 것입니다. 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 : 안드로이드 문서는 한 업데이트경고 이 문제에 대한. 따라서 이상한 행동이 남아 있습니다. 그러나 이제 문서화되었습니다.


20
이것은 나를 죽이고 있었고, 나는 내 마음을 잃고 있다고 생각했다. 이 솔루션을 게시 해 주셔서 감사합니다!
Brad Hein

10
이 게시물은 거대합니다. 많은 덕분에 디버깅에 몇 시간이 걸릴 수 있습니다!
Kevin Gaudin

굉장히, 이것은 내가 필요한 것입니다. 좋은 설명도!
stealthcopter

이것은 이미 나를 물었고, 나는이 게시물을 읽을 때까지 무슨 일이 있었는지 몰랐습니다. 감사합니다! 부 안드로이드!
Nakedible

5
좋은 답변, 감사합니다. 문서에서 분명히 언급해야합니다. code.google.com/p/android/issues/detail?id=48589
Andrey Chernih

16

이 수락 된 대답은 괜찮 습니다. 활동이 재개 될 때마다 새 인스턴스를 만들고 있습니다.

그래서 활동 내에서 리스너에 대한 참조를 유지하는 것은 어떻습니까?

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

}

이것은 우리가 단단한 참조를 유지하는 것을 제외하고는 당신이하고있는 것과 매우 유사합니다.


super.onResume()전에 사용 getPreferenceScreen()...했습니까?
Yousha Aleayoub

@YoushaAleayoub, android.app.supernotcalledexception 에 대해 읽으십시오 .Android 구현에 필요합니다.
사무엘

무슨 소리 야? 사용 super.onResume()하기 전에 또는 사용하기 전에 getPreferenceScreen()사용해야합니까? 내가 올바른 장소에 대해 이야기하고 있기 때문입니다. cs.dartmouth.edu/~campbell/cs65/lecture05/lecture05.html
Yousha Aleayoub

developer.android.com/training/basics/activity-lifecycle/… 여기에서 읽은 것을 기억 하십시오. 코드의 주석을 참조하십시오. 그러나 꼬리에 두는 것은 논리적입니다. 그러나 지금까지 나는 이것에 아무런 문제가 없었습니다.
사무엘

감사합니다. onResume () 및 onPause () 메소드가 등록 this하고 등록 하지 않은 다른 곳에서는 listener오류가 발생하여 문제를 해결할 수 있습니다. 그러나이 두 가지 방법은 현재 공개되어 있으며 보호되지 않습니다.
Nicolas

16

이 주제에 대한 가장 자세한 페이지이므로 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;
}

참고 OnSharedPreferenceChangeListener 요구를 등록하는 것은이 경우에 PreferenceActivity를 생성 후 수행 할 것으로는, 다른 핸들러는 주요 활동에 호출되지 않습니다! 그것을 깨닫는 데 약간의 시간이 걸렸습니다 ...


9

전화를받을 SharedPreferenceChangeListener때마다 수락 된 답변이 생성 됩니다 onResume. @SamuelSharedPreferenceListener 은 Activity 클래스의 멤버를 만들어 해결합니다 . 그러나 Google 이이 코드 랩 에서 사용 하는 세 번째 및보다 간단한 솔루션 이 있습니다 . 액티비티 클래스가 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);
    }
}

1
정확히, 이것이되어야합니다. 인터페이스를 구현하고, onStart에 등록하고, onStop에 등록 취소하십시오.
Junaed

2

저장된 키에서 변경이 발생할 때 감지하는 SharedPreferenceChangeListener 등록을위한 Kotlin 코드 :

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

이 코드를 onStart () 또는 다른 곳에 넣을 수 있습니다. * 사용할 것을 고려하십시오

 if(key=="YourKey")

또는 sharedPreferences의 다른 키에서 발생하는 모든 변경에 대해 "// Do Something"블록 내의 코드가 잘못 실행됩니다.


1

따라서 이것이 실제로 누군가를 도울 수 있는지 모르겠습니다. 문제가 해결되었습니다. 비록 내가 받아 들인 대답에OnSharedPreferenceChangeListener 명시된대로 구현했지만 . 그래도 청취자의 전화가 불일치했습니다.

나는 안드로이드가 얼마 후 가비지 수집을 위해 그것을 보낸다는 것을 이해하기 위해 여기에 왔습니다. 그래서 코드를 살펴 보았습니다. 부끄러운 일로 , 리스너 GLOBALLY를 선언하지 않고 대신 onCreateView. 리스너를 로컬 변수로 변환하라는 Android Studio를 들었 기 때문입니다.


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 콜백 처리 비용을 줄 입니다.


-3

첫 번째 앱에서 공유하는 Word 읽기 가능한 데이터를 읽는 동안

바꾸다

getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);

getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);

두 번째 응용 프로그램에서 두 번째 응용 프로그램의 값을 업데이트하십시오.

그러나 여전히 작동하지 않습니다 ...


Android는 여러 프로세스에서 SharedPreferences에 액세스하는 것을 지원하지 않습니다. 이렇게하면 동시성 문제가 발생하여 모든 기본 설정이 손실 될 수 있습니다. 또한 MODE_MULTI_PROCESS는 더 이상 지원되지 않습니다.
Sam

@ Sam이 답변은 3 세이므로 최신 버전의 Android에서 작동하지 않으면 다운 투표하지 마십시오. 답변이 작성된 시간은 그렇게하는 것이 가장 좋은 방법이었습니다.
shridutt kothari

1
아니요.이 답변을 작성한 경우에도 그 방법은 다중 프로세스 안전이 아닙니다.
Sam

@Sam에서 알 수 있듯이 공유 환경 설정은 절대 안전하게 처리되지 않았습니다. 또한 kothari를 shridutt하기 위해-downvotes가 마음에 들지 않으면 잘못된 답변을 제거하십시오 (어쨌든 OP의 질문에 대답하지 않습니다). 그러나 프로세스 안전 방식으로 공유 환경 설정을 계속 사용하려면 프로세스 안전 추상화 위에 프로세스 안전 추상화를 작성해야합니다. 즉 ContentProvider는 프로세스 안전하며 계속해서 공유 환경 설정을 지속 스토리지 메커니즘으로 사용할 수 있습니다. 이전 에이 작업을 수행했으며 작은 데이터 세트 / 환경 설정의 경우 sqlite가 공정한 마진으로 수행됩니다.
Mark Keen

1
@MarkKeen 실제로, 나는 이것을 위해 콘텐츠 제공자를 사용하려고 시도했지만 Android 버그로 인해 프로덕션에서 콘텐츠 제공자가 무작위로 실패하는 경향이 있었으므로 요즘에는 브로드 캐스트를 사용하여 필요에 따라 구성을 보조 프로세스에 동기화합니다.
Sam
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.