源代码说明
虽然此处的其他好的答案已正确指出该潜在问题记录在SharedPreferences.getStringSet()中,但基本上是“不要修改返回的Set,因为不能保证行为”,但我想实际贡献对于任何想深入研究的人来说,都会导致此问题/行为的源代码。
看一下SharedPreferencesImpl(Android Pie的源代码),我们可以看到SharedPreferencesImpl.commitToMemory()
原始值(Set<String>
在我们的例子中为a)和新修改的值之间存在比较:
private MemoryCommitResult commitToMemory() {
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
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
从此内存映射中获取a 。如果随后将值添加到同一Set
实例,则正在修改内存中的Map。然后当你调用editor.putStringSet()
,并尝试提交,commitToMemory()
被执行,和比较线尝试比较新修改的值,v
以existingValue
基本上是在内存中相同Set
就像刚才修改的一个。对象实例是不同的,因为Set
已经在不同的地方复制了,但是内容是相同的。
因此,您正在尝试将新数据与旧数据进行比较,但是您已经通过直接修改该Set
实例无意中更新了旧数据。因此,您的新数据将不会写入文件。
但是,为什么这些值最初存储后在应用程序被终止后消失了?
如OP所述,似乎值是在测试应用程序时存储的,但是新值在终止应用程序进程并重新启动后会消失。这是因为在应用程序运行且您要添加值时,您仍在将这些值添加到内存Set
结构中,而在调用时,getStringSet()
您将获得相同的内存Set
。您所有的值都在那里,并且看起来正在运行。但是在终止应用程序之后,此内存结构以及所有新值都将被销毁,因为它们从未写入文件中。
解
正如其他人所说,只是避免修改内存结构,因为这基本上是在引起副作用。因此,当您致电getStringSet()
并希望重复使用内容作为起点时,只需将内容复制到另一个Set
实例中,而不是直接修改它:new HashSet<>(getPrefs().getStringSet())
。现在,当比较发生时,内存existingValue
实际上将与修改后的值不同v
。