SharedPreferences
API を使用して一連の文字列を保存しようとしています。
Set<String> s = sharedPrefs.getStringSet("key", new HashSet<String>());
s.add(new_element);
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putStringSet(s);
edit.commit()
上記のコードを初めて実行すると、s
がデフォルト値(作成されたばかりの最後の空のHashSet
)に設定され、問題なく保存されます。
このコードを2回目に実行すると、最初の要素が追加されたs
オブジェクトが返されます。要素を追加できます。プログラムの実行中にSharedPreferences
に保存されているようですが、プログラムが強制終了されると、SharedPreferences
が永続ストレージから再度読み取られ、新しい値が失われた。
2番目以降の要素を失わないように保存するにはどうすればよいですか?
この「問題」は SharedPreferences.getStringSet
で文書化されています。
SharedPreferences.getStringSet
は、SharedPreferences
内に格納されているHashSetオブジェクトの参照を返します。このオブジェクトに要素を追加すると、実際にはSharedPreferences
内に追加されます。
それは大丈夫ですが、それを保存しようとすると問題が発生します:Android SharedPreferences.Editor.putStringSet
を使用して保存しようとしている変更されたHashSetとSharedPreference
、および両方が同じオブジェクトです!!!
可能な解決策は、SharedPreferences
オブジェクトによって返されるSet<String>
のコピーを作成することです。
Set<String> s = new HashSet<String>(sharedPrefs.getStringSet("key", new HashSet<String>()));
これにより、s
は別のオブジェクトになり、s
に追加された文字列は、SharedPreferences
内に格納されているセットに追加されません。
動作する他の回避策は、同じSharedPreferences.Editor
トランザクションを使用して別のより単純な設定(整数またはブール値など)を保存することです。必要なのは、保存された値が各トランザクションで異なることを強制することです(たとえば、文字列セットのサイズを保存できます)。
この動作は文書化されているため、仕様によるものです。
getStringSetから:
「この呼び出しによって返されるセットインスタンスを変更しないでください。保存したデータの一貫性は保証されません。また、インスタンスを変更する能力もまったく保証されません。」
また、特にAPIで文書化されている場合は非常に合理的です。そうでない場合、このAPIはアクセスごとにコピーを作成する必要があります。したがって、この設計の理由はおそらくパフォーマンスにあります。この関数は、変更不可能なクラスインスタンスにラップされた結果を返すようにする必要がありますが、これには再度割り当てが必要です。
同じ問題の解決策を探していましたが、次の方法で解決しました。
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();
上記の回答をすべて試してみましたが、私にはうまくいきませんでした。そこで、次の手順を実行しました
コピーに存在する値を、新しいものとして扱うクリアされた共有設定に追加します。
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(); }
バックグラウンドからアプリを削除した後にアプリを再度開くと、リストに追加された最初の要素のみが表示された場合、値が永続的ではないという問題に直面していました。
ここでの他の良い答えは、この潜在的な問題が 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);
}
基本的にここで起こっているのは、ファイルに変更を書き込もうとすると、このコードは変更/追加されたキー/値のペアをループし、それらが既に存在するかどうかを確認し、存在しないか存在する場合にのみファイルに書き込みますメモリに読み込まれた既存の値とは異なります。
ここで注意を払うべき重要な行は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
と比較しようとします修正したばかりです。 Set
sはさまざまな場所にコピーされているため、オブジェクトインスタンスは異なりますが、内容は同じです。
そのため、新しいデータを古いデータと比較しようとしていますが、Set
インスタンスを直接変更することで、古いデータを意図せずに更新しています。したがって、新しいデータはファイルに書き込まれません。
OPが述べたように、アプリのテスト中に値が保存されているように見えますが、アプリプロセスを強制終了して再起動すると、新しい値は消えます。これは、アプリの実行中に値を追加しているときに、stillがメモリ内のSet
構造体に値を追加し、 getStringSet()
を呼び出すと、これと同じインメモリSet
が返されます。すべての価値があり、機能しているように見えます。ただし、アプリを強制終了すると、このメモリ内構造は、ファイルに書き込まれなかったため、すべての新しい値とともに破棄されます。
他の人が述べたように、メモリ内の構造を変更することは避けてください。基本的に副作用を引き起こすからです。したがって、getStringSet()
を呼び出して、コンテンツを開始点として再利用する場合は、コンテンツを直接変更するのではなく、別のSet
インスタンスにコピーしてください:new HashSet<>(getPrefs().getStringSet())
。これで比較が行われると、メモリ内のexistingValue
は実際に変更された値v
と異なります。