SharedPreferences를 사용하여 문자열 세트를 저장하려고 할 때의 오작동
SharedPreferences
API를 사용하여 문자열 집합을 저장하려고합니다 .
Set<String> s = sharedPrefs.getStringSet("key", new HashSet<String>());
s.add(new_element);
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putStringSet(s);
edit.commit()
위의 코드를 처음 실행하면 s
기본값 (방금 생성 된 끝 비어 있음 HashSet
)으로 설정되어 문제없이 저장됩니다.
두 번째 및 다음에이 코드를 실행 s
하면 첫 번째 요소가 추가 된 개체가 반환됩니다. 요소를 추가 할 수 있으며 프로그램 실행 중에에는 분명히에 저장 SharedPreferences
되지만 프로그램이 종료되면 SharedPreferences
영구 저장소에서 다시 읽히고 최신 값이 손실됩니다.
두 번째 요소와 그 이후의 요소를 저장하여 손실되지 않도록하려면 어떻게해야합니까?
이 "문제"는에 문서화되어 SharedPreferences.getStringSet
있습니다.
는 SharedPreferences.getStringSet
내부에 저장된 HashSet 개체의 참조를 반환합니다 SharedPreferences
. 이 객체에 요소를 추가하면 실제로 SharedPreferences
.
괜찮지 만 저장하려고 할 때 문제가 발생합니다. Android는 저장하려는 수정 된 HashSet을에 SharedPreferences.Editor.putStringSet
저장된 현재 HashSet과 비교 SharedPreference
하며 둘 다 동일한 객체입니다 !!!
가능한 해결책은 객체 가 Set<String>
반환 한 복사본을 만드는 것입니다 SharedPreferences
.
Set<String> s = new HashSet<String>(sharedPrefs.getStringSet("key", new HashSet<String>()));
이는 s
다른 객체를 만들고에 추가 된 문자열 s
은 SharedPreferences
.
작동 할 다른 해결 방법은 동일한 SharedPreferences.Editor
트랜잭션 을 사용하여 다른 간단한 기본 설정 (예 : 정수 또는 부울)을 저장하는 것입니다. 필요한 유일한 것은 저장된 값이 각 트랜잭션에서 달라 지도록하는 것입니다 (예 : 문자열을 저장할 수 있음). 크기 설정).
이 동작은 문서화되어 있으므로 의도적으로 설계된 것입니다.
getStringSet에서 :
"이 호출에서 반환 된 집합 인스턴스를 수정해서는 안됩니다. 이렇게하면 저장된 데이터의 일관성이 보장되지 않으며 인스턴스를 수정할 수있는 능력도 전혀 보장되지 않습니다."
특히 API에 문서화되어있는 경우 매우 합리적으로 보입니다. 그렇지 않으면이 API가 각 액세스에 대해 사본을 만들어야합니다. 그래서이 디자인의 이유는 아마도 성능 때문일 것입니다. 이 함수가 수정 불가능한 클래스 인스턴스에 래핑 된 결과를 반환해야한다고 생각하지만 다시 한 번 할당이 필요합니다.
동일한 문제에 대한 해결책을 찾고 있었으며 다음 방법으로 해결했습니다.
1) 공유 환경 설정에서 기존 세트 검색
2) 사본 만들기
3) 사본 업데이트
4) 사본 저장
SharedPreferences.Editor editor = sharedPrefs.edit();
Set<String> oldSet = sharedPrefs.getStringSet("key", new HashSet<String>());
//make a copy, update it and save it
Set<String> newStrSet = new HashSet<String>();
newStrSet.add(new_element);
newStrSet.addAll(oldSet);
editor.putStringSet("key",newStrSet); edit.commit();
위의 모든 답변을 시도했지만 아무도 효과가 없었습니다. 그래서 다음 단계를 수행했습니다.
- 이전 공유 환경 목록에 새 요소를 추가하기 전에 사본을 만드십시오.
- 해당 메서드에 대한 매개 변수로 위의 복사본을 사용하여 메서드를 호출합니다.
- 해당 메서드 내부에서 해당 값을 보유하고있는 공유 환경을 지 웁니다.
add the values present in copy to the cleared shared preference it will treat it as new.
public static void addCalcsToSharedPrefSet(Context ctx,Set<String> favoriteCalcList) { ctx.getSharedPreferences(FAV_PREFERENCES, 0).edit().clear().commit(); SharedPreferences sharedpreferences = ctx.getSharedPreferences(FAV_PREFERENCES, Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedpreferences.edit(); editor.putStringSet(FAV_CALC_NAME, favoriteCalcList); editor.apply(); }
I was facing issue with the values not being persistent, if i reopen the app after cleaning the app from background only first element added to the list was shown.
Source Code Explanation
While the other good answers on here have correctly pointed out that this potential issue is documented in SharedPreferences.getStringSet(), basically "Don't modify the returned Set because the behavior isn't guaranteed", I'd like to actually contribute the source code that causes this problem/behavior for anyone that wants to dive deeper.
Taking a look at SharedPreferencesImpl (source code as of Android Pie) we can see that in SharedPreferencesImpl.commitToMemory()
there is a comparison that occurs between the original value (a Set<String>
in our case) and the newly modified value:
private MemoryCommitResult commitToMemory() {
// ... other code
// mModified is a Map of all the key/values added through the various put*() methods.
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// ... other code
// mapToWriteToDisk is a copy of the in-memory Map of our SharedPreference file's
// key/value pairs.
if (mapToWriteToDisk.containsKey(k)) {
Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mapToWriteToDisk.put(k, v);
}
So basically what's happening here is that when you try to write your changes to file this code will loop through your modified/added key/value pairs and check if they already exist, and will only write them to file if they don't or are different from the existing value that was read into memory.
The key line to pay attention to here is if (existingValue != null && existingValue.equals(v))
. You're new value will only be written to disk if existingValue
is null
(doesn't already exist) or if existingValue
's contents are different from the new value's contents.
This the the crux of the issue. existingValue
is read from memory. The SharedPreferences file that you are trying to modify is read into memory and stored as Map<String, Object> mMap;
(later copied into mapToWriteToDisk
each time you try to write to file). When you call getStringSet()
you get back a Set
from this in-memory Map. If you then add a value to this same Set
instance, you are modifying the in-memory Map. Then when you call editor.putStringSet()
and try to commit, commitToMemory()
gets executed, and the comparison line tries to compare your newly modified value, v
, to existingValue
which is basically the same in-memory Set
as the one you've just modified. The object instances are different, because the Set
s have been copied in various places, but the contents are identical.
So you're trying to compare your new data to your old data, but you've already unintentionally updated your old data by directly modifying that Set
instance. Thus your new data will not be written to file.
But why are the values stored initially but disappear after the app is killed?
As the OP stated, it seems as if the values are stored while you're testing the app, but then the new values disappear after you kill the app process and restart it. This is because while the app is running and you're adding values, you're still adding the values to the in-memory Set
structure, and when you call getStringSet()
you're getting back this same in-memory Set
. All your values are there and it looks like it's working. But after you kill the app, this in-memory structure is destroyed along with all the new values since they were never written to file.
Solution
As others have stated, just avoid modifying the in-memory structure, because you're basically causing a side-effect. So when you call getStringSet()
and want to reuse the contents as a starting point, just copy the contents into a different Set
instance instead of directly modifying it: new HashSet<>(getPrefs().getStringSet())
. Now when the comparison happens, the in-memory existingValue
will actually be different from your modified value v
.
'Program Tip' 카테고리의 다른 글
codeigniter에서 base_url () 함수가 작동하지 않습니다. (0) | 2020.11.26 |
---|---|
CSV 파일에 대해 쉼표와 큰 따옴표를 동시에 이스케이프하는 방법은 무엇입니까? (0) | 2020.11.26 |
Java에서 매개 변수로 함수 전달 (0) | 2020.11.26 |
C ++의 함수 이름 : 대문자 사용 여부? (0) | 2020.11.26 |
C # 일반 사전에서 값 필터링 (0) | 2020.11.26 |