源码说明
虽然这里的其他好的答案正确地指出这个潜在的问题记录在SharedPreferences.getStringSet()中,基本上“不要修改返回的 Set,因为不能保证行为”,但我想实际贡献对于任何想要深入研究的人来说,导致此问题/行为的源代码。
查看SharedPreferencesImpl(Android Pie 的源代码),我们可以看到原始值(在我们的例子中为 a)和新修改的值SharedPreferencesImpl.commitToMemory()
之间发生了比较:Set<String>
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);
}
所以基本上这里发生的事情是,当您尝试将更改写入文件时,此代码将遍历您修改/添加的键/值对并检查它们是否已经存在,并且只会在它们不存在或不存在时将它们写入文件不同于读入内存的现有值。
这里要注意的关键线是if (existingValue != null && existingValue.equals(v))
。existingValue
如果是null
(不存在)或者如果existingValue
的内容与新值的内容不同,则您的新值只会被写入磁盘。
这是问题的症结所在。 existingValue
从内存中读取。您尝试修改的 SharedPreferences 文件被读入内存并存储为(稍后在您每次尝试写入文件时Map<String, Object> mMap;
复制到该文件中)。mapToWriteToDisk
当你打电话时,getStringSet()
你会Set
从这个内存地图中得到一个。如果您随后向同一Set
实例添加值,则您正在修改内存映射。然后,当您调用editor.putStringSet()
并尝试提交时,它commitToMemory()
会被执行,并且比较行会尝试比较您新修改的值,v
,existingValue
它与您刚刚修改的内存Set
中的值基本相同。对象实例不同,因为Set
s 被复制到不同的地方,但内容是相同的。
因此,您尝试将新数据与旧数据进行比较,但您已经通过直接修改该Set
实例无意中更新了旧数据。因此,您的新数据不会被写入文件。
但是为什么最初存储的值在应用程序被杀死后消失了呢?
正如 OP 所说,似乎在您测试应用程序时存储了这些值,但是在您终止应用程序进程并重新启动它之后,新值就会消失。这是因为当应用程序正在运行并且您正在添加值时,您仍在将值添加到 in-memorySet
结构中,并且当您调用时,getStringSet()
您将返回相同的 in-memory Set
。你所有的价值观都在那里,看起来它正在发挥作用。但是在你杀死应用程序之后,这个内存结构和所有新值一起被破坏,因为它们从未被写入文件。
解决方案
正如其他人所说,只要避免修改内存结构,因为你基本上会造成副作用。因此,当您调用getStringSet()
并希望重用内容作为起点时,只需将内容复制到不同的Set
实例中,而不是直接修改它:new HashSet<>(getPrefs().getStringSet())
. 现在,当比较发生时,内存中existingValue
的值实际上与您修改后的值不同v
。