问题描述
使用SharedPreferences的putStringSet方法之后,发现数据并没有保存,并且确定调用了apply()或commit()方法,而且putString,putInt,putLong等方法都能正常保存。
原因
SharedPreferences接口是由SharedPreferencesImpl类实现的,其中putStringSet方法如下:
@Override
public Editor putStringSet(String key, @Nullable Set<String> values) {
synchronized (mEditorLock) {
mModified.put(key,
(values == null) ? null : new HashSet<String>(values));
return this;
}
}
可以看到stringSet存储在mModified变量中,然后当调用apply()或commit()方法时,最后都会调用到commitToMemory()方法,如下:
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
if (mDiskWritesInFlight > 0) {
mMap = new HashMap<String, Object>(mMap);
}
mapToWriteToDisk = mMap;
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (mEditorLock) {
boolean changesMade = false;
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
if (v == this || v == null) {
if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
mapToWriteToDisk.remove(k);
} else {
if (mapToWriteToDisk.containsKey(k)) {
Object existingValue = mapToWriteToDisk.get(k);
//注意这里
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mapToWriteToDisk.put(k, v);
}
changesMade = true;
if (hasListeners) {
keysModified.add(k);
}
}
mModified.clear();
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
可以看到在该方法中,会遍历mModified,将其中的值和mapToWriteToDisk中的相比较,当值和value都相等时,则跳出循环,进行下一次比较。mapToWriteToDisk的值来自于全局变量mMap,mMap的值在loadFromDisk()方法中加载,如下:
private void loadFromDisk() {
//代码省略....
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
map = (Map<String, Object>) XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
// An errno exception means the stat failed. Treat as empty/non-existing by
// ignoring.
} catch (Throwable t) {
thrown = t;
}
//代码省略....
}
也就是说mMap或者mapToWriteToDisk的值是之前存起来的值。之前说过putString,putInt等都可以正常存值,所以stringSet应该是在existingValue.equals(v)
这句校验失败的,存的值使用了getStringSet方法返回的值,如下:
//注意:错误代码演示!!!
SharedPreferences preferences = context.getSharedPreferences(SP_FILE_NAME, Context.MODE_PRIVATE);
Set<String> set = preferences.getStringSet("test", new HashSet<String>());
SharedPreferences.Editor editor = preferences.edit();
set.add("value");
//这样的代码无效,因为就算set中的值发生了改变,但还是同一个对象
editor.putStringSet("test", set);
editor.apply();
解决方案
不要直接使用getStringSet返回的set,需要重新new一个set,如下所示
SharedPreferences preferences = context.getSharedPreferences(SP_FILE_NAME, Context.MODE_PRIVATE);
//重新new一个set对象,将getStringSet返回的set添加进去而不是直接使用
Set<String> set = new HashSet<String>();
set.addAll(preferences.getStringSet("test", new HashSet<String>()));
SharedPreferences.Editor editor = preferences.edit();
set.add("value");
editor.putStringSet("test", set);
editor.apply();
网友评论