Android の SharedPreferences データはオンメモリで保持されている

SharedPreferences 関連の不具合を調べていてAOSP のコードを見ていたら、SharedPreferences の挙動について気づいたので Tips としてメモしておきます。

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

多くの場合、ActivityApplication から 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 に保存したデータを取得する時、ファイルアクセス等のオーバヘッドを気にする必要はありません。
がんばってアプリでキャッシュしていた皆さんは残念でした。そのコード、消していいですよ。

コメント

このブログの人気の投稿

Lumia 920買った - LTE有効化と開発端末登録

Android で Unicode絵文字入力

Nexus 5 買った