74

我正在尝试使用SharedPreferencesAPI存储一组字符串。

Set<String> s = sharedPrefs.getStringSet("key", new HashSet<String>());
s.add(new_element);

SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putStringSet(s);
edit.commit()

我第一次执行上面的代码时,s设置为默认值(刚刚创建的 end empty HashSet)并且存储没有问题。

第二次和下一次执行此代码时,将s返回一个添加了第一个元素的对象。我可以添加元素,并且在程序执行期间,它显然存储在 中SharedPreferences,但是当程序被终止时,SharedPreferences再次从其持久存储中读取并且更新的值会丢失。

如何存储第二个以及之后的元素,以免丢失?

4

6 回答 6

169

这个“问题”记录在SharedPreferences.getStringSet.

SharedPreferences.getStringSet返回 .hashSet 中存储的 HashSet 对象的引用SharedPreferences。当您向该对象添加元素时,它们实际上是添加到SharedPreferences.

没关系,但是当您尝试存储它时问题就来了:Android 将您尝试保存使用的修改后的 HashSetSharedPreferences.Editor.putStringSet与存储在 上的当前哈希集进行比较SharedPreference,两者都是同一个对象!!!

Set<String>一种可能的解决方案是制作对象返回的副本SharedPreferences

Set<String> s = new HashSet<String>(sharedPrefs.getStringSet("key", new HashSet<String>()));

这会产生s一个不同的对象,并且添加到的字符串s不会添加到存储在SharedPreferences.

其他可行的解决方法是使用相同的SharedPreferences.Editor事务来存储另一个更简单的首选项(如整数或布尔值),您唯一需要的是强制每个事务的存储值不同(例如,您可以存储字符串设置大小)。

于 2012-12-25T23:51:33.137 回答
13

此行为已记录在案,因此是设计使然:

从getStringSet:

“请注意,您不得修改此调用返回的集合实例。如果您这样做,则无法保证存储数据的一致性,您也无法修改实例。”

这似乎很合理,特别是如果它记录在 API 中,否则这个 API 必须在每次访问时进行复制。所以这个设计的原因可能是性能。我想他们应该让这个函数返回结果包装在不可修改的类实例中,但这又需要分配。

于 2012-12-26T08:07:41.247 回答
5

正在寻找相同问题的解决方案,通过以下方式解决:

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();

为什么

于 2016-11-18T01:15:36.293 回答
5

源码说明

虽然这里的其他好的答案正确地指出这个潜在的问题记录在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()会被执行,并且比较行会尝试比较您新修改的值,vexistingValue它与您刚刚修改的内存Set中的值基本相同。对象实例不同,因为Sets 被复制到不同的地方,但内容是相同的。

因此,您尝试将新数据与旧数据进行比较,但您已经通过直接修改该Set实例无意中更新了旧数据。因此,您的新数据不会被写入文件。

但是为什么最初存储的值在应用程序被杀死后消失了呢?

正如 OP 所说,似乎在您测试应用程序时存储了这些值,但是在您终止应用程序进程并重新启动它之后,新值就会消失。这是因为当应用程序正在运行并且您正在添加值时,您仍在将值添加到 in-memorySet结构中,并且当您调用时,getStringSet()您将返回相同的 in-memory Set。你所有的价值观都在那里,看起来它正在发挥作用。但是在你杀死应用程序之后,这个内存结构和所有新值一起被破坏,因为它们从未被写入文件。

解决方案

正如其他人所说,只要避免修改内存结构,因为你基本上会造成副作用。因此,当您调用getStringSet()并希望重用内容作为起点时,只需将内容复制到不同的Set实例中,而不是直接修改它:new HashSet<>(getPrefs().getStringSet()). 现在,当比较发生时,内存中existingValue的值实际上与您修改后的值不同v

于 2019-06-12T01:14:12.013 回答
2

我尝试了上述所有答案,但都没有为我工作。所以我做了以下步骤

  1. 在将新元素添加到旧共享首选项列表之前,请对其进行复制
  2. 调用具有上述副本的方法作为该方法的参数。
  3. 在该方法中清除持有该值的共享首选项。
  4. 将副本中存在的值添加到已清除的共享首选项中,它将视为新的。

    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(); }
    

我遇到了值不持久的问题,如果我在从后台清理应用程序后重新打开应用程序,则只显示添加到列表中的第一个元素。

于 2017-09-23T10:27:46.433 回答
1

就像一个注释,共享首选项不能只是被覆盖。如果你已经给它赋值了,你必须先通过方法移除它remove(KEY),然后再commit()销毁这个键。然后,您可以为其分配一个新值。

https://developer.android.com/reference/android/content/SharedPreferences.html#getStringSet(java.lang.String,%20java.util.Set%3Cjava.lang.String%3E)

于 2021-06-24T05:34:54.617 回答