Android の SharedPreferences データはオンメモリで保持されている
SharedPreferences 関連の不具合を調べていてAOSP のコードを見ていたら、SharedPreferences の挙動について気づいたので Tips としてメモしておきます。
SharedPreferencesImpl (Android 5.0.1)
http://tools.oesf.biz/android-5.0.1_r1.0/xref/frameworks/base/core/java/android/app/SharedPreferencesImpl.java
このクラスではコンストラクタでXMLファイルからデータを読み込み、Map<String, Object> でSharedPreferences の中身のデータを保持し続けています。
ファイルアクセスやXMLパースのオーバヘッドを気にして、読み込んだ値をアプリ内で変数保持している人も多そうな気がしますが、上記の通りオンメモリで読み出されるので実はほとんど意味がありません。
巨大なオブジェクトを Serialize や Jsonize して保存しているような場合は復元処理が必要なので覚えている意味もありますが、それは SharedPreferences 使わないほうがよいのでは?という気がします。
ContextImpl (Android 5.0.1)
http://tools.oesf.biz/android-5.0.1_r1.0/xref/frameworks/base/core/java/android/app/ContextImpl.java#900
ContextImpl では static な ArrayMap<String, ArrayMap<String, SharedPreferences>> (Jelly bean 以前は ArrayMap の代わりに HashMap)で一度取得した SharedPreferences インスタンスをオンメモリに保持し続けています。
つまり、アプリは SharedPreferences をメンバ変数として保持する必要はなく、ローカル変数の使い捨てでも構わないわけです。もちろん、メンバ変数で持っていたほうがアクセスが楽だと判断するならばそうすべきですが。
がんばってアプリでキャッシュしていた皆さんは残念でした。そのコード、消していいですよ。
SharedPrefereces に保存した設定値は読み捨てでOK
Androidアプリで設定等の小さいデータを保存するのに使用する SharedPreferences ですが、Android Framework 内では SharedPreferencesImpl というクラスで実装されています。SharedPreferencesImpl (Android 5.0.1)
http://tools.oesf.biz/android-5.0.1_r1.0/xref/frameworks/base/core/java/android/app/SharedPreferencesImpl.java
このクラスではコンストラクタでXMLファイルからデータを読み込み、Map<String, Object> でSharedPreferences の中身のデータを保持し続けています。
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
map = XmlUtils.readMapXml(str);
} catch (XmlPullParserException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (FileNotFoundException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (IOException e) {
Log.w(TAG, "getSharedPreferences", e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
}
mLoaded = true;
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<String, Object>();
}
getInt(String ,int) や getString(String, String) 等の値を読み出すメソッドを使用したとき、値はストレージからではなくメモリ上の Map<String, Object> から読み出されることになります。ファイルアクセスやXMLパースのオーバヘッドを気にして、読み込んだ値をアプリ内で変数保持している人も多そうな気がしますが、上記の通りオンメモリで読み出されるので実はほとんど意味がありません。
巨大なオブジェクトを Serialize や Jsonize して保存しているような場合は復元処理が必要なので覚えている意味もありますが、それは SharedPreferences 使わないほうがよいのでは?という気がします。
SharedPreferences のインスタンスすらローカル変数の使い捨てOK
多くの場合、Activity や Application から getSharedPreferences(String) で SharedPreferences のインスタンスを取得するかと思いますが、その実態は ContextImpl クラスに実装されています。ContextImpl (Android 5.0.1)
http://tools.oesf.biz/android-5.0.1_r1.0/xref/frameworks/base/core/java/android/app/ContextImpl.java#900
ContextImpl では static な ArrayMap<String, ArrayMap<String, SharedPreferences>> (Jelly bean 以前は ArrayMap の代わりに HashMap)で一度取得した SharedPreferences インスタンスをオンメモリに保持し続けています。
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
if (sSharedPrefs == null) {
sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
}
final String packageName = getPackageName();
ArrayMap packagePrefs = sSharedPrefs.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
sSharedPrefs.put(packageName, packagePrefs);
}
getSharedPreferences(String) が呼ばれるとこの Map からインスタンスを引き出してきて返すので、SharedPreferencesImpl のコンストラクタでのファイルアクセスとXMLパースも発生しません。つまり、アプリは SharedPreferences をメンバ変数として保持する必要はなく、ローカル変数の使い捨てでも構わないわけです。もちろん、メンバ変数で持っていたほうがアクセスが楽だと判断するならばそうすべきですが。
まとめ
SharedPreferences に保存したデータを取得する時、ファイルアクセス等のオーバヘッドを気にする必要はありません。がんばってアプリでキャッシュしていた皆さんは残念でした。そのコード、消していいですよ。
コメント
コメントを投稿