深入理解Android中的SharedPreferences

Wei_Leng發表於2016-09-26

SharedPreferences作為Android中資料儲存方式的一種,我們經常會用到,它適合用來儲存那些少量的資料,特別是鍵值對資料,比如配置資訊,登入資訊等。不過要想做到正確使用SharedPreferences,就需要弄清楚下面幾個問題: 
(1)每次呼叫getSharedPreferences時都會建立一個SharedPreferences物件嗎?這個物件具體是哪個類物件? 
(2)在UI執行緒中呼叫getXXX有可能導致ANR嗎? 
(3)為什麼SharedPreferences只適合用來存放少量資料,為什麼不能把SharedPreferences對應的xml檔案當成普通檔案一樣存放大量資料? 
(4)commit和apply有什麼區別? 
(5)SharedPreferences每次寫入時是增量寫入嗎?

要想弄清楚上面幾個問題,需要檢視SharedPreferences的原始碼實現才能解決。先從Context的getSharedPreferences開始:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> SharedPreferences <span class="hljs-title" style="box-sizing: border-box;">getSharedPreferences</span>(String name, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> mode) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> mBase.getSharedPreferences(name, mode);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>

我們知道Android中的Context類體系其實是使用了裝飾者模式,而被裝飾物件就這個mBase,它其實就是一個ContextImpl物件,ContextImpl的getSharedPreferences方法:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> SharedPreferences <span class="hljs-title" style="box-sizing: border-box;">getSharedPreferences</span>(String name, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> mode) {
        SharedPreferencesImpl sp;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">synchronized</span> (ContextImpl.class) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (sSharedPrefs == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                sSharedPrefs = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
            }

            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String packageName = getPackageName();
            ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (packagePrefs == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                packagePrefs = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> ArrayMap<String, SharedPreferencesImpl>();
                sSharedPrefs.put(packageName, packagePrefs);
            }

            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// At least one application in the world actually passes in a null</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// name.  This happened to work because when we generated the file name</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// we would stringify it to "null.xml".  Nice.</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mPackageInfo.getApplicationInfo().targetSdkVersion <
                    Build.VERSION_CODES.KITKAT) {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (name == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                    name = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"null"</span>;
                }
            }

            sp = packagePrefs.get(name);
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (sp == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                File prefsFile = getSharedPrefsFile(name);
                sp = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> SharedPreferencesImpl(prefsFile, mode);
                packagePrefs.put(name, sp);
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> sp;
            }
        }
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ((mode & Context.MODE_MULTI_PROCESS) != <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> ||
            getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// If somebody else (some other process) changed the prefs</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// file behind our back, we reload it.  This has been the</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// historical (if undocumented) behavior.</span>
            sp.startReloadIfChangedUnexpectedly();
        }
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> sp;
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li></ul>

可以看到這裡使用到了單例模式,sSharedPrefs 是一個ArrayMap,packagePrefs也是一個ArrayMap,它們的關係是這樣的: 
packagePrefs存放檔案name與SharedPreferencesImpl鍵值對,sSharedPrefs存放包名與ArrayMap鍵值對。注意sSharedPrefs是static變數,也就是一個類只有一個例項,因此你每次getSharedPreferences其實拿到的都是同一個SharedPreferences物件。這裡回答第一個問題,對於一個相同的SharedPreferences name,獲取到的都是同一個SharedPreferences物件,它其實是SharedPreferencesImpl物件。

SharedPreferencesImpl構造方法:

<code class="hljs mel has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;">    SharedPreferencesImpl(File <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">file</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> mode) {
        mFile = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">file</span>;
        mBackupFile = makeBackupFile(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">file</span>);
        mMode = mode;
        mLoaded = false;
        mMap = null;
        startLoadFromDisk();
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>

與mBackupFile有關的等後面說,看startLoadFromDisk方法:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">startLoadFromDisk</span>() {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">synchronized</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>) {
            mLoaded = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;
        }
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Thread(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"SharedPreferencesImpl-load"</span>) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">run</span>() {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">synchronized</span> (SharedPreferencesImpl.<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>) {
                    loadFromDiskLocked();
                }
            }
        }.start();
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li></ul>

實際上是呼叫loadFromDiskLocked方法:

<code class="hljs lasso has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">void</span> loadFromDiskLocked() {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mLoaded) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span>;
        }
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mBackupFile<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>exists()) {
            mFile<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>delete();
            mBackupFile<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>renameTo(mFile);
        }

        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Debugging</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mFile<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>exists() <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">&&</span> <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">!</span>mFile<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>canRead()) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">Log</span><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>w(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">TAG</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Attempt to read preferences file "</span> <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">+</span> mFile <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">+</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">" without permission"</span>);
        }

        <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">Map</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">map</span> <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">null</span>;
        StructStat stat <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">null</span>;
        try {
            stat <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span> Os<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>stat(mFile<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>getPath());
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mFile<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>canRead()) {
                BufferedInputStream str <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">null</span>;
                try {
                    str <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">new</span> BufferedInputStream(
                            <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">new</span> FileInputStream(mFile), <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">16</span><span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">*</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1024</span>);
                    <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">map</span> <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span> XmlUtils<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>readMapXml(str);
                } catch (XmlPullParserException e) {
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">Log</span><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>w(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">TAG</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"getSharedPreferences"</span>, e);
                } catch (FileNotFoundException e) {
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">Log</span><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>w(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">TAG</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"getSharedPreferences"</span>, e);
                } catch (IOException e) {
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">Log</span><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>w(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">TAG</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"getSharedPreferences"</span>, e);
                } finally {
                    IoUtils<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>closeQuietly(str);
                }
            }
        } catch (ErrnoException e) {
        }
        mLoaded <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">true</span>;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">map</span> <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">!=</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">null</span>) {
            mMap <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">map</span>;
            mStatTimestamp <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span> stat<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>st_mtime;
            mStatSize <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span> stat<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>st_size;
        } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
            mMap <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">=</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">new</span> HashMap<span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;"><</span><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span>, Object<span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">></span>();
        }
        notifyAll();
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li></ul>

可以看到對於一個SharedPreferences檔案name,第一次呼叫getSharedPreferences時會去建立一個SharedPreferencesImpl物件,它會開啟一個子執行緒,然後去把指定的SharedPreferences檔案中的鍵值對全部讀取出來,存放在一個Map中。如果我們在UI執行緒中這樣子寫:

<code class="hljs vhdl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;">SharedPreferences sp = getSharedPreferences(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"test"</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">Context</span>.MODE_PRIVATE);
<span class="hljs-typename" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span> name = sp.getString(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"name"</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

呼叫getString時那個SharedPreferencesImpl構造方法開啟的子執行緒可能還沒執行完(比如檔案比較大時全部讀取會比較久),這時getString當然還不能獲取到相應的值,必須阻塞到那個子執行緒讀取完為止,getString方法:

<code class="hljs javascript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;">public <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span> getString(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span> key, <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span> defValue) {
        synchronized (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>) {
            awaitLoadedLocked();
            <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span> v = (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span>)mMap.get(key);
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> v != <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">null</span> ? v : defValue;
        }
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul>

顯然這個awaitLoadedLocked方法就是用來等this這個鎖的,在loadFromDiskLocked方法的最後我們也可以看到它呼叫了notifyAll方法,這時如果getString之前阻塞了就會被喚醒。那麼現在這裡有一個問題,我們的getString是寫在UI執行緒中,如果那個getString被阻塞太久了,比如60s,這時就會出現ANR,因此要根據具體情況考慮是否需要把SharedPreferences的讀寫放在子執行緒中。這裡回答第二個問題,在UI執行緒中呼叫getXXX可能會導致ANR。同時可以回答第三個問題,SharedPreferences只能用來存放少量資料,如果一個SharedPreferences對應的xml檔案很大的話,在初始化時會把這個檔案的所有資料都載入到記憶體中,這樣就會佔用大量的記憶體,有時我們只是想讀取某個xml檔案中一個key的value,結果它把整個檔案都載入進來了,顯然如果必要的話這裡需要進行相關優化處理。

SharedPreferences的getXXX的實現基本都是一樣,這裡就不逐個分析了。

SharedPreferences的初始化和讀取比較簡單,寫操作就相對複雜了點,我們知道寫一個SharedPreferences檔案都是先要呼叫edit方法獲取到一個Editor物件:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> Editor <span class="hljs-title" style="box-sizing: border-box;">edit</span>() {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">synchronized</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>) {
            awaitLoadedLocked();
        }
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> EditorImpl();</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>

其實拿到的是一個EditorImpl物件,它是SharedPreferencesImpl的內部類:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">EditorImpl</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">implements</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">Editor</span> {</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> Map<String, Object> mModified = Maps.newHashMap();
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> mClear = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;
        ......
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>

可以看到它有一個Map物件mModified,用來儲存“髒資料”,也就是你每次put的時候其實是把那個鍵值對放到這個mModified 中,最後呼叫apply或者commit才會真正把資料寫入檔案中,比如看putString:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> Editor <span class="hljs-title" style="box-sizing: border-box;">putString</span>(String key, String <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">value</span>) {
            synchronized (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>) {
                mModified.put(key, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">value</span>);
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>;
            }
        }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>

其它putXXX程式碼基本也是一樣的。EditorImpl類的關鍵就是apply和commit,不過它們有一些區別,先看commit方法:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> boolean <span class="hljs-title" style="box-sizing: border-box;">commit</span>() {
            MemoryCommitResult mcr = commitToMemory();
            SharedPreferencesImpl.<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>.enqueueDiskWrite(
                mcr, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* sync write on this thread okay */</span>);
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">try</span> {
                mcr.writtenToDiskLatch.<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">await</span>();
            } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">catch</span> (InterruptedException e) {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;
            }
            notifyListeners(mcr);
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> mcr.writeToDiskResult;
        }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li></ul>

關鍵有兩步,先呼叫commitToMemory,再呼叫enqueueDiskWrite,commitToMemory就是產生一個“合適”的MemoryCommitResult物件mcr,然後呼叫enqueueDiskWrite時需要把這個物件傳進去,commitToMemory方法:

<code class="hljs javascript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;">private MemoryCommitResult commitToMemory() {
            MemoryCommitResult mcr = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> MemoryCommitResult();
            synchronized (SharedPreferencesImpl.this) {
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// We optimistically don't make a deep copy until</span>
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// a memory commit comes in when we're already</span>
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// writing to disk.</span>
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mDiskWritesInFlight > <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>) {
                    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// We can't modify our mMap as a currently</span>
                    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// in-flight write owns it.  Clone it before</span>
                    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// modifying it.</span>
                    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// noinspection unchecked</span>
                    mMap = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> HashMap<<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span>, <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">Object</span>>(mMap);
                }
                mcr.mapToWriteToDisk = mMap;
                mDiskWritesInFlight++;

                boolean hasListeners = mListeners.size() > <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>;
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (hasListeners) {
                    mcr.keysModified = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> ArrayList<<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span>>();
                    mcr.listeners =
                            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
                }

                synchronized (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>) {
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mClear) {
                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!mMap.isEmpty()) {
                            mcr.changesMade = <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">true</span>;
                            mMap.clear();
                        }
                        mClear = <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">false</span>;
                    }

                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (Map.Entry<<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span>, <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">Object</span>> e : mModified.entrySet()) {
                        <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span> k = e.getKey();
                        <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">Object</span> v = e.getValue();
                        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// "this" is the magic value for a removal mutation. In addition,</span>
                        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// setting a value to "null" for a given key is specified to be</span>
                        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// equivalent to calling remove on that key.</span>
                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (v == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span> || v == <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">null</span>) {
                            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!mMap.containsKey(k)) {
                                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">continue</span>;
                            }
                            mMap.remove(k);
                        } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
                            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mMap.containsKey(k)) {
                                <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">Object</span> existingValue = mMap.get(k);
                                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (existingValue != <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">null</span> && existingValue.equals(v)) {
                                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">continue</span>;
                                }
                            }
                            mMap.put(k, v);
                        }

                        mcr.changesMade = <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">true</span>;
                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (hasListeners) {
                            mcr.keysModified.add(k);
                        }
                    }

                    mModified.clear();
                }
            }
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> mcr;
        }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li><li style="box-sizing: border-box; padding: 0px 5px;">51</li><li style="box-sizing: border-box; padding: 0px 5px;">52</li><li style="box-sizing: border-box; padding: 0px 5px;">53</li><li style="box-sizing: border-box; padding: 0px 5px;">54</li><li style="box-sizing: border-box; padding: 0px 5px;">55</li><li style="box-sizing: border-box; padding: 0px 5px;">56</li><li style="box-sizing: border-box; padding: 0px 5px;">57</li><li style="box-sizing: border-box; padding: 0px 5px;">58</li><li style="box-sizing: border-box; padding: 0px 5px;">59</li><li style="box-sizing: border-box; padding: 0px 5px;">60</li><li style="box-sizing: border-box; padding: 0px 5px;">61</li><li style="box-sizing: border-box; padding: 0px 5px;">62</li><li style="box-sizing: border-box; padding: 0px 5px;">63</li><li style="box-sizing: border-box; padding: 0px 5px;">64</li></ul>

這裡需要弄清楚兩個物件mMap和mModified,mMap是存放當前SharedPreferences檔案中的鍵值對,而mModified是存放此時edit時put進去的鍵值對。mDiskWritesInFlight表示正在等待寫的運算元量。可以看到這個方法中首先處理了clear標誌,它呼叫的是mMap.clear(),然後再遍歷mModified將新的鍵值對put進mMap,也就是說在一次commit事務中,如果同時put一些鍵值對和呼叫clear,那麼clear掉的只是之前的鍵值對,這次put進去的鍵值對還是會被寫入的。遍歷mModified時,需要處理一個特殊情況,就是如果一個鍵值對的value是this(SharedPreferencesImpl)或者是null那麼表示將此鍵值對刪除,這個在remove方法中可以看到:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> Editor <span class="hljs-title" style="box-sizing: border-box;">remove</span>(String key) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">synchronized</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>) {
                mModified.put(key, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>;
            }
        }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>

commit接下來就是呼叫enqueueDiskWrite方法:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">enqueueDiskWrite</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> MemoryCommitResult mcr,
                                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> Runnable postWriteRunnable) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> Runnable writeToDiskRunnable = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Runnable() {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">run</span>() {
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">synchronized</span> (mWritingToDiskLock) {
                        writeToFile(mcr);
                    }
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">synchronized</span> (SharedPreferencesImpl.<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>) {
                        mDiskWritesInFlight--;
                    }
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (postWriteRunnable != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                        postWriteRunnable.run();
                    }
                }
            };

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> isFromSyncCommit = (postWriteRunnable == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>);

        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Typical #commit() path with fewer allocations, doing a write on</span>
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// the current thread.</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (isFromSyncCommit) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> wasEmpty = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">synchronized</span> (SharedPreferencesImpl.<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>) {
                wasEmpty = mDiskWritesInFlight == <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>;
            }
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (wasEmpty) {
                writeToDiskRunnable.run();
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span>;
            }
        }

        QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li></ul>

先定義一個Runnable,注意實現Runnable與繼承Thread的區別,Runnable表示一個任務,不一定要在子執行緒中執行,一般優先考慮使用Runnable。這個Runnable中先呼叫writeToFile進行寫操作,寫操作需要先獲得mWritingToDiskLock,也就是寫鎖。然後執行mDiskWritesInFlight–,表示正在等待寫的操作減少1。最後判斷postWriteRunnable是否為null,呼叫commit時它為null,而呼叫apply時它不為null。 
Runnable定義完,就判斷這次是commit還是apply,如果是commit,即isFromSyncCommit為true,而且有1個寫操作需要執行,那麼就呼叫writeToDiskRunnable.run(),注意這個呼叫是在當前執行緒中進行的。如果不是commit,那就是apply,這時呼叫QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable),這個QueuedWork類其實很簡單,裡面有一個SingleThreadExecutor,用於非同步執行這個writeToDiskRunnable。 
這裡就可以回答第四個問題了,commit的寫操作是在呼叫執行緒中執行的,而apply內部是用一個單執行緒的執行緒池實現的,因此寫操作是在子執行緒中執行的。

說一下那個mBackupFile,SharedPreferences在寫入時會先把之前的xml檔案改成名成一個備份檔案,然後再將要寫入的資料寫到一個新的檔案中,如果這個過程執行成功的話,就會把備份檔案刪除。由此可見每次即使只是新增一個鍵值對,也會重新寫入整個檔案的資料,這也說明SharedPreferences只適合儲存少量資料,檔案太大會有效能問題。這裡回答第五個問題,SharedPreferences每次寫入都是整個檔案重新寫入,不是增量寫入。

SharedPreferences幾種模式: 
Context.MODE_PRIVATE:應用私有,只有相同的UID才能進行讀寫 
Context.MODE_MULTI_PROCESS:多程式安全標誌,Android2.3之前該標誌是預設被設定的,Android2.3開始需要自己設定。 
MODE_APPEND:首次建立時如果檔案存在不會刪除檔案。 
注意這些模式可以使用位與進行設定,比如MODE_PRIVATE | MODE_APPEND。

相關文章