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