深入理解Android中的SharedPreferences
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。
相關文章
- android中sharedPreferences的用法Android
- 深入理解 Android 中的 MatrixAndroid
- Android SharedPreferences 實現原理解析Android
- 深入理解Android中的ClassLoaderAndroid
- 瞭解 Android API 中的 SharedPreferencesAndroidAPI
- Android 深入理解Android中的自定義屬性Android
- 深入理解 Android 中的各種 ContextAndroidContext
- Android中的資料儲存之SharedPreferencesAndroid
- Android -SharedPreferencesAndroid
- 深入理解AndroidAndroid
- Android中SharedPreferences使用方法介紹Android
- Android 之 SharedPreferencesAndroid
- 帶你深入理解Android中的自定義屬性!!!Android
- Android中深入理解 LayoutInflater.inflate()Android
- JS中this的深入理解JS
- 深入理解Js中的thisJS
- android: SharedPreferences儲存Android
- 深入理解Java中的鎖Java
- 深入理解 Java 中的 LambdaJava
- 深入理解Java中的AQSJavaAQS
- 深入理解 JavaScript 中的 classJavaScript
- 深入理解Oracle中的DBCAOracle
- 深入理解Oracle中的MutexOracleMutex
- 深入理解Oracle中的latchOracle
- 深入理解Android中的快取機制(三)磁碟快取Android快取
- Android SharedPreferences最佳實踐Android
- Android SharedPreferences 原始碼分析Android原始碼
- Android儲存之SharedPreferencesAndroid
- 深入理解Java中的逃逸分析Java
- 深入理解Java中的鎖(一)Java
- 深入理解Java中的鎖(二)Java
- 深入理解Java中的Garbage CollectionJava
- 深入理解python中的yieldPython
- 深入理解JavaScript中的箭頭JavaScript
- 深入理解gradle中的taskGradle
- 深入理解JavaScirpt中的this(轉)Java
- 深入理解JVM中的ClassLoaderJVM
- 深入理解 JavaScript 中的函式JavaScript函式