Android源碼分析之SharedPreferences

文章轉載自:http://www.it165.net/pro/html/201406/15827.html

Android的日常開發中,相信大家都用過SharedPreferences來保存用戶的某些settings值。Shared Preferences以鍵值對的形式存儲私有的原生類型數據,這裏的私有的是指只對你自己的app可見的,也就是說別的app是無法訪問到的。客戶端代碼爲了使用它有2種方式,一種是通過Context#getSharedPreferences(String prefName, int mode)方法,另一種是Activity自己的getPreferences(int mode)方法,其內部還是調用了前者只是用activity的類名做了prefName而已,我們先來看下Conext#getSharedPreferences的內部實現。其具體實現在ContextImpl.java文件中,代碼如下:

01.@Override
02.public SharedPreferences getSharedPreferences(String name, int mode) {
03.SharedPreferencesImpl sp; // 這個是我們接下來要分析的重點類
04.synchronized (ContextImpl.class) {
05.if (sSharedPrefs == null) { // sSharedPrefs是一個靜態的ArrayMap,注意這個類型,表示一個包可以對應有一組SharedPreferences
06.sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
07.// ArrayMap<String, SharedPreferencesImpl>表示文件名到SharedpreferencesImpl的映射關係
08. 
09.final String packageName = getPackageName(); // 先通過包名找到與之關聯的prefs集合packagePrefs
10.ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
11.if (packagePrefs == null) { // lazy initialize
12.packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
13.sSharedPrefs.put(packageName, packagePrefs); // 添加到全局sSharedPrefs中
14.}
15. 
16.// At least one application in the world actually passes in a null
17.// name.  This happened to work because when we generated the file name
18.// we would stringify it to "null.xml".  Nice.
19.if (mPackageInfo.getApplicationInfo().targetSdkVersion <
20.Build.VERSION_CODES.KITKAT) {
21.if (name == null) {
22.name = "null"// name傳null時候的特殊處理,用"null"代替
23.}
24.}
25. 
26.sp = packagePrefs.get(name); // 再找與文件名name關聯的sp對象;
27.if (sp == null) {            // 如果還沒有,
28.File prefsFile = getSharedPrefsFile(name); // 則先根據name構建一個prefsFile對象
29.sp = new SharedPreferencesImpl(prefsFile, mode); // 再new一個SharedPreferencesImpl對象的實例
30.packagePrefs.put(name, sp); // 並添加到packagePrefs中
31.return sp; // 第一次直接return
32.}
33.}
34.// 如果不是第一次,則在<a href="http://www.it165.net/pro/ydad/" target="_blank" class="keylink">Android</a>3.0之前或者mode設置成了MULTI_PROCESS的話,調用reload
35.if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
36.getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
37.// If somebody else (some other process) changed the prefs
38.// file behind our back, we reload it.  This has been the
39.// historical (if undocumented) behavior.
40.sp.startReloadIfChangedUnexpectedly(); // 將硬盤中最新的改動重新加載到內存中
41.}
42.return sp; // 最後返回SharedPreferences的具體對象sp
43.}

通過分析這段代碼我們大體能得到2個重要結論:

1. 靜態的ArrayMap變量sSharedPrefs,因爲它一直伴隨我們的app存在,所以如果你的SharedPreferences很多的話,map會很大,從而會佔用較大部分內存;一般來說,你可以將多個小的prefs文件合併到一個稍大的裏面。

2. 當你用SharedPreferences來跨進程通信的時候,你會發現你不能像往常(非MODE_MULTI_PROCESS的情況)那樣,調用一次getSharedPreferences方法然後用這個實例來讀取值。因爲如果你不是每次調用getSharedPreferences方法的話,此方法最後的那段reload代碼不會被執行,那麼可能別的進程寫的最新數據在你的進程裏面還是看不到(本人項目親歷)。而且reload雖然不在UI線程中操作但畢竟也是耗時(費力)的IO操作,所以Android doc關於Context.MODE_MULTI_PROCESS字段的說明中也明確提及有更好的跨進程通信方式。

看SharedPreferences的源碼我們知道它只是一個接口而已,在其內部又有2個嵌套的接口:OnSharedPreferenceChangeListener和Editor;前者代表了回調接口,表示當一個shared preference改變時如果你感興趣則有能力收聽到通知;Editor則定義了用來寫值的接口,而用來讀數據的方法都在大的SharedPreferences接口中定義。它們的具體實現在SharedPreferencesImpl.java文件中。

下面就讓我們睜大眼睛,好好研究下這個類具體是怎麼實現的。和以往一樣,我們還是從關鍵字段和ctor開始,源碼如下:

01.// Lock ordering rules: // 這3行註釋明確寫明瞭加鎖的順序,注意下;在我們自己的代碼裏如果遇到類似
02.//  - acquire SharedPreferencesImpl.this before EditorImpl.this // (需要多把鎖)的情況,則最好也寫清楚,
03.//  - acquire mWritingToDiskLock before EditorImpl.this         // 這是個很好的習慣,方便別人看你的代碼。
04. 
05.private final File mFile; // 我們的shared preferences背後存儲在這個文件裏
06.private final File mBackupFile; // 與mFile對應的備份文件
07.private final int mMode; // 如MODE_PRIVATE,MODE_WORLD_READABLE,MODE_WORLD_WRITEABLE,MODE_MULTI_PROCESS等
08. 
09.private Map<String, Object> mMap;     // guarded by 'this' 將settings緩存在內存中的map
10.private int mDiskWritesInFlight = 0;  // guarded by 'this' 表示還未寫到disk中的寫操作的數目
11.private boolean mLoaded = false;      // guarded by 'this' 表示settings整個從disk加載到內存map中完畢的標誌
12.private long mStatTimestamp;          // guarded by 'this' 文件的最近一次更新時間
13.private long mStatSize;               // guarded by 'this' 文件的size,注意這些字段都被this對象保護
14. 
15.private final Object mWritingToDiskLock = new Object(); // 寫操作的鎖對象

接着我們看看其構造器:

1.SharedPreferencesImpl(File file, int mode) {
2.mFile = file;
3.mBackupFile = makeBackupFile(file); // 根據file,產生一個.bak的File對象
4.mMode = mode;
5.mLoaded = false;
6.mMap = null;
7.startLoadFromDisk();
8.}

構造器也比較簡單,主要做2件事情,初始化重要變量&將文件異步加載到內存中。

下面我們緊接着看下將settings文件異步加載到內存中的操作:

01.private void startLoadFromDisk() {
02.synchronized (this) {
03.mLoaded = false// 開始load前,將其reset(加鎖),後面的loadFromDiskLocked方法會檢測這個標記
04.}
05.new Thread("SharedPreferencesImpl-load") {
06.public void run() {
07.synchronized (SharedPreferencesImpl.this) {
08.loadFromDiskLocked(); // 在一個新的線程中開始load,注意鎖加在SharedPreferencesImpl對象上,
09.}                         // 也就是說這時候如果其他線程調用SharedPreferences.getXXX之類的方法都會被阻塞。
10.}
11.}.start();
12.}
13. 
14.private void loadFromDiskLocked() { // 此方法受SharedPreferencesImpl.this鎖的保護
15.if (mLoaded) { // 如果已加載完畢則直接返回
16.return;
17.}
18.if (mBackupFile.exists()) {
19.mFile.delete(); // 如果備份文件存在,則刪除(非備份)文件mFile,
20.mBackupFile.renameTo(mFile); // 將備份文件重命名爲mFile(相當於mFile現在又存在了只是內容其實已經變成了mBackupFile而已)
21.}                                // 或者說接下來的讀操作實際是從備份文件中來的
22. 
23.// Debugging
24.if (mFile.exists() && !mFile.canRead()) {
25.Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
26.}
27. 
28.Map map = null;
29.StructStat stat = null;
30.try {
31.stat = Libcore.os.stat(mFile.getPath()); // 得到文件的一系列信息,有linux c經驗的同學應該都很眼熟
32.if (mFile.canRead()) { // 前提是文件可讀啊。。。一般都是成立的,否則我們最終會得到一個空的map
33.BufferedInputStream str = null;
34.try {
35.str = new BufferedInputStream(
36.new FileInputStream(mFile), 16*1024);
37.map = XmlUtils.readMapXml(str); // 用str中所有xml信息構造一個map返回
38.catch (XmlPullParserException e) {
39.Log.w(TAG, "getSharedPreferences", e);
40.catch (FileNotFoundException e) {
41.Log.w(TAG, "getSharedPreferences", e);
42.catch (IOException e) {
43.Log.w(TAG, "getSharedPreferences", e);
44.finally {
45.IoUtils.closeQuietly(str);
46.}
47.}
48.catch (ErrnoException e) {
49.}
50.mLoaded = true// 標記加載過了
51.if (map != null) {
52.mMap = map; // 如果map非空,則設置mMap,並更新文件訪問時間、文件大小字段
53.mStatTimestamp = stat.st_mtime;
54.mStatSize = stat.st_size;
55.else {
56.mMap = new HashMap<String, Object>(); // 否則初始化一個empty的map
57.}
58.notifyAll(); // 最後通知所有阻塞在SharedPreferencesImpl.this對象上的線程數據ready了,可以往下進行了
59.}

接下來我們看看將文件reload進內存的方法:

01.void startReloadIfChangedUnexpectedly() {
02.synchronized (this) { // 也是在SharedPreferencesImpl.this對象上加鎖
03.// TODO: wait for any pending writes to disk?
04.if (!hasFileChangedUnexpectedly()) { // 如果沒有我們之外的意外更改,則直接返回,因爲我們的數據
05.return;                          // 仍然是最新的,沒必要reload
06.}
07.startLoadFromDisk(); // 真正需要reload
08.}
09.}
10. 
11.// Has the file changed out from under us?  i.e. writes that
12.// we didn't instigate.
13.private boolean hasFileChangedUnexpectedly() { // 這個方法檢測是否別的進程也修改了文件
14.synchronized (this) {
15.if (mDiskWritesInFlight > 0) { // 知道是我們自己引起的,則直接返回false,表示是預期的
16.// If we know we caused it, it's not unexpected.
17.if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
18.return false;
19.}
20.}
21. 
22.final StructStat stat;
23.try {
24./*
25.* Metadata operations don't usually count as a block guard
26.* violation, but we explicitly want this one.
27.*/
28.BlockGuard.getThreadPolicy().onReadFromDisk();
29.stat = Libcore.os.stat(mFile.getPath());
30.catch (ErrnoException e) {
31.return true;
32.}
33. 
34.synchronized (this) { // 比較文件的最近更新時間和size是否和我們手頭的一樣,如果不一樣則說明有unexpected修改
35.return mStatTimestamp != stat.st_mtime || mStatSize != stat.st_size;
36.}
37.}

接下來要分析的是一堆讀操作相關的,各種getXXX,它們做的事情本質都是一樣的,不一個個分析了,只說下大體思想:在同步塊中等待加載完成,然後直接從mMap中返回需要的信息,而不是每次都觸發一次讀文件操作(本人沒看源碼之前一直以爲是讀文件操作),這裏我們只看下block等待的方法:

01.private void awaitLoadedLocked() { // 注意此方法也是在SharedPreferencesImpl.this鎖的保護下
02.if (!mLoaded) {
03.// Raise an explicit StrictMode onReadFromDisk for this
04.// thread, since the real read will be in a different
05.// thread and otherwise ignored by StrictMode.
06.BlockGuard.getThreadPolicy().onReadFromDisk();
07.}
08.while (!mLoaded) { // 當條件變量不成立時(即沒load完成)則無限等待
09.try {          // 注意這個經典的形式我們已經見到好幾次了(上一次是在HandlerThread中,還記得?)
10.wait();
11.catch (InterruptedException unused) {
12.}
13.}
14.}

接下來我們看看真正修改(寫)文件的操作是怎麼實現的,代碼如下:

001.// Return value from EditorImpl#commitToMemory()
002.private static class MemoryCommitResult { // 此靜態類表示EditorImpl#commitToMemory()的返回值
003.public boolean changesMade;  // any keys different?
004.public List<String> keysModified;  // may be null
005.public Set<OnSharedPreferenceChangeListener> listeners;  // may be null
006.public Map<?, ?> mapToWriteToDisk; // 要寫到disk中的map(持有數據的map)
007.public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); // 初始化爲1的count down閉鎖
008.public volatile boolean writeToDiskResult = false;
009. 
010.public void setDiskWriteResult(boolean result) { // 結束寫操作的時候調用,result爲true表示成功
011.writeToDiskResult = result;
012.writtenToDiskLatch.countDown(); // 此調用會釋放所有block在await調用上的線程
013.}
014.}
015. 
016.public final class EditorImpl implements Editor { // Editor的具體實現類
017.private final Map<String, Object> mModified = Maps.newHashMap(); // 持有所有要修改的數據即調用putXXX方法時提供的參數
018.private boolean mClear = false;
019. 
020.public Editor putString(String key, String value) {
021.synchronized (this) { // EditorImpl.this鎖用來保護mModified對象
022.mModified.put(key, value); // 修改不是立即寫到文件中的,而是暫時放在內存的map中的
023.return this// 返回當前對象,以便支持鏈式方法調用
024.}
025.}
026.public Editor putStringSet(String key, Set<String> values) {
027.synchronized (this) {
028.mModified.put(key,
029.(values == null) ? null new HashSet<String>(values));
030.return this;
031.}
032.}
033.public Editor putInt(String key, int value) {
034.synchronized (this) {
035.mModified.put(key, value);
036.return this;
037.}
038.}
039.public Editor putLong(String key, long value) {
040.synchronized (this) {
041.mModified.put(key, value);
042.return this;
043.}
044.}
045.public Editor putFloat(String key, float value) {
046.synchronized (this) {
047.mModified.put(key, value);
048.return this;
049.}
050.}
051.public Editor putBoolean(String key, boolean value) {
052.synchronized (this) {
053.mModified.put(key, value);
054.return this;
055.}
056.}
057. 
058.public Editor remove(String key) {
059.synchronized (this) {
060.mModified.put(key, this); // 注意remove操作比較特殊,remove一個key時會put一個特殊的this對象,
061.return this;              // 後面的commitToMemory方法對此有特殊處理
062.}
063.}
064. 
065.public Editor clear() {
066.synchronized (this) {
067.mClear = true;
068.return this;
069.}
070.}
071. 
072.public void apply() {
073.final MemoryCommitResult mcr = commitToMemory();
074.final Runnable awaitCommit = new Runnable() {
075.public void run() {
076.try {
077.mcr.writtenToDiskLatch.await(); // block等待寫操作完成
078.catch (InterruptedException ignored) {
079.}
080.}
081.};
082. 
083.QueuedWork.add(awaitCommit); // 將awaitCommit添加到QueueWork中;這裏順帶引出一個疑問:那麼apply方法到底
084.// 會不會導致SharedPreferences丟失數據更新呢?(有興趣的同學可以看看QueuedWork#waitToFinish方法都在哪裏,
085.// 什麼情況下被調用了就明白了)
086. 
087.Runnable postWriteRunnable = new Runnable() { // 寫操作完成之後要執行的runnable
088.public void run() {
089.awaitCommit.run(); // 執行awaitCommit runnable並從QueueWork中移除
090.QueuedWork.remove(awaitCommit);
091.}
092.};
093. 
094.SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); // 準備將mcr寫到磁盤中
095. 
096.// Okay to notify the listeners before it's hit disk
097.// because the listeners should always get the same
098.// SharedPreferences instance back, which has the
099.// changes reflected in memory.
100.notifyListeners(mcr);
101.}
102. 
103.// Returns true if any changes were made
104.private MemoryCommitResult commitToMemory() { // 當此方法調用時,這裏有2級鎖,先是SharedPreferencesImpl.this鎖,
105.MemoryCommitResult mcr = new MemoryCommitResult(); // 然後是EditorImpl.this鎖,所以當commit的時候任何調用getXXX
106.synchronized (SharedPreferencesImpl.this) {// 的方法都會block。此方法的目的主要是構造一個合適的MemoryCommitResult對象。
107.// We optimistically don't make a deep copy until //
108.// a memory commit comes in when we're already
109.// writing to disk.
110.if (mDiskWritesInFlight > 0) {
111.// We can't modify our mMap as a currently
112.// in-flight write owns it.  Clone it before
113.// modifying it.
114.// noinspection unchecked
115.mMap = new HashMap<String, Object>(mMap); // 當有多個寫操作等待執行時make a copy of mMap
116.}
117.mcr.mapToWriteToDisk = mMap;
118.mDiskWritesInFlight++; // 表示又多了一個(未完成的)寫操作
119. 
120.boolean hasListeners = mListeners.size() > 0;
121.if (hasListeners) {
122.mcr.keysModified = new ArrayList<String>();
123.mcr.listeners =
124.new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
125.}
126. 
127.synchronized (this) { // 加鎖在EditorImpl對象上
128.if (mClear) { // 處理clear的情況
129.if (!mMap.isEmpty()) {
130.mcr.changesMade = true;
131.mMap.clear();
132.}
133.mClear = false// reset
134.// 注意這裏由於先處理了clear操作,所以clear並不會清掉本次寫操作的數據,只會clear掉以前有的數據
135. 
136.for (Map.Entry<String, Object> e : mModified.entrySet()) { // 遍歷mModified處理各個key、value
137.String k = e.getKey();
138.Object v = e.getValue();
139.if (v == this) {  // magic value for a removal mutation // 這個就是標記爲刪除的特殊value
140.if (!mMap.containsKey(k)) {
141.continue;
142.}
143.mMap.remove(k); // 從mMap中刪除
144.else {
145.boolean isSame = false;
146.if (mMap.containsKey(k)) {
147.Object existingValue = mMap.get(k);
148.if (existingValue != null && existingValue.equals(v)) {
149.continue;
150.}
151.}
152.mMap.put(k, v); // 將mModified中的值更新到mMap中
153.}
154. 
155.mcr.changesMade = true// 走到這步表示有更新產生
156.if (hasListeners) {
157.mcr.keysModified.add(k);
158.}
159.}
160. 
161.mModified.clear(); // 一次commit執行完後清空mModified,準備接下來的put操作
162.}
163.}
164.return mcr;
165.}
166. 
167.public boolean commit() {
168.MemoryCommitResult mcr = commitToMemory();
169.SharedPreferencesImpl.this.enqueueDiskWrite( // 發起寫操作
170.mcr, null /* sync write on this thread okay */);
171.try // block等待寫操作完成,如果是UI線程可能會造成UI卡頓,所以Android建議我們如果不關心返回值可以考慮用apply替代
172.mcr.writtenToDiskLatch.await();
173.catch (InterruptedException e) {
174.return false;
175.}
176.notifyListeners(mcr);
177.return mcr.writeToDiskResult;
178.}
179. 
180.private void notifyListeners(final MemoryCommitResult mcr) { // 注意此方法中callback調用永遠發生在UI線程中
181.if (mcr.listeners == null || mcr.keysModified == null ||
182.mcr.keysModified.size() == 0) {
183.return;
184.}
185.if (Looper.myLooper() == Looper.getMainLooper()) {
186.for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
187.final String key = mcr.keysModified.get(i);
188.for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
189.if (listener != null) {
190.listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
191.}
192.}
193.}
194.else {
195.// Run this function on the main thread.
196.ActivityThread.sMainThreadHandler.post(new Runnable() {
197.public void run() {
198.notifyListeners(mcr);
199.}
200.});
201.}
202.}
203.}

最後我們看下SharedPreferencesImpl的最後3個重要方法(也即真正寫操作發生的地方):

001./**
002.* Enqueue an already-committed-to-memory result to be written
003.* to disk.
004.*
005.* They will be written to disk one-at-a-time in the order
006.* that they're enqueued.
007.*
008.* @param postWriteRunnable if non-null, we're being called
009.*   from apply() and this is the runnable to run after
010.*   the write proceeds.  if null (from a regular commit()),
011.*   then we're allowed to do this disk write on the main
012.*   thread (which in addition to reducing allocations and
013.*   creating a background thread, this has the advantage that
014.*   we catch them in userdebug StrictMode reports to convert
015.*   them where possible to apply() ...)
016.*/
017.private void enqueueDiskWrite(final MemoryCommitResult mcr, // 此方法的doc寫的很詳細,你可以仔細閱讀下
018.final Runnable postWriteRunnable) {
019.final Runnable writeToDiskRunnable = new Runnable() { // 真正寫操作的runnable
020.public void run() {
021.synchronized (mWritingToDiskLock) { // 第3把鎖,保護寫操作的
022.writeToFile(mcr);
023.}
024.synchronized (SharedPreferencesImpl.this) {
025.mDiskWritesInFlight--; // 表示1個寫操作完成了,少了1個in flight的了
026.}
027.if (postWriteRunnable != null) {
028.postWriteRunnable.run(); // 如果非空則執行之(apply的時候滿足)
029.}
030.}
031.};
032. 
033.final boolean isFromSyncCommit = (postWriteRunnable == null); // 判斷我們是否從commit方法來的
034. 
035.// Typical #commit() path with fewer allocations, doing a write on
036.// the current thread.
037.if (isFromSyncCommit) {
038.boolean wasEmpty = false;
039.synchronized (SharedPreferencesImpl.this) {
040.wasEmpty = mDiskWritesInFlight == 1// 如果mDiskWritesInFlight是1的話表示有1個寫操作需要執行
041.}
042.if (wasEmpty) { // 在UI線程中直接調用其run方法執行之
043.writeToDiskRunnable.run();
044.return// 執行完畢後返回
045.}
046.}
047.// 否則來自apply調用的話,直接扔一個writeToDiskRunnable給單線程的thread executor去執行
048.QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
049.}
050.// 依據file創建與之對應的文件(在文件系統中)
051.private static FileOutputStream createFileOutputStream(File file) {
052.FileOutputStream str = null;
053.try {
054.str = new FileOutputStream(file);
055.catch (FileNotFoundException e) {
056.File parent = file.getParentFile();
057.if (!parent.mkdir()) {
058.Log.e(TAG, "Couldn't create directory for SharedPreferences file " + file);
059.return null;
060.}
061.FileUtils.setPermissions(
062.parent.getPath(),
063.FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
064.-1, -1);
065.try {
066.str = new FileOutputStream(file);
067.catch (FileNotFoundException e2) {
068.Log.e(TAG, "Couldn't create SharedPreferences file " + file, e2);
069.}
070.}
071.return str;
072.}
073. 
074.// Note: must hold mWritingToDiskLock
075.private void writeToFile(MemoryCommitResult mcr) {
076.// Rename the current file so it may be used as a backup during the next read
077.if (mFile.exists()) { // 如果對應的mFile存在的話,針對於非第一次操作
078.if (!mcr.changesMade) {
079.// If the file already exists, but no changes were
080.// made to the underlying map, it's wasteful to
081.// re-write the file.  Return as if we wrote it
082.// out.
083.mcr.setDiskWriteResult(true); // 沒有什麼改動發生調用此方法結束,因爲沒啥可寫的
084.return;
085.}
086.if (!mBackupFile.exists()) { // 如果沒備份文件存在的話,嘗試將mFile重命名爲mBackupFile
087.// 因爲如果本次寫操作失敗的話(可能這時數據已經不完整了或破壞掉了),下次再讀的話還可以從備份文件中恢復
088.if (!mFile.renameTo(mBackupFile)) { // 如果重命名失敗則調用mcr.setDiskWriteResult(false)結束
089.Log.e(TAG, "Couldn't rename file " + mFile
090." to backup file " + mBackupFile);
091.mcr.setDiskWriteResult(false);
092.return;
093.}
094.else // 備份文件存在的話,則刪除mFile(因爲接下來我們馬上要重新寫一個新mFile了)
095.mFile.delete();
096.}
097.}
098. 
099.// Attempt to write the file, delete the backup and return true as atomically as
100.// possible.  If any exception occurs, delete the new file; next time we will restore
101.// from the backup.
102.try {
103.FileOutputStream str = createFileOutputStream(mFile); // 嘗試創建mFile
104.if (str == null) { // 如果失敗則調用mcr.setDiskWriteResult(false)收場
105.mcr.setDiskWriteResult(false);
106.return;
107.}
108.XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); // 將mcr的mapToWriteToDisk全部寫到str對應的文件中
109.FileUtils.sync(str); // 將buffer中的數據都flush到底層設備中
110.str.close(); // 關閉文件流
111.ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); // 設置文件權限根據mMode
112.try {
113.final StructStat stat = Libcore.os.stat(mFile.getPath());
114.synchronized (this) {
115.mStatTimestamp = stat.st_mtime; // 同步更新文件相關的2個變量
116.mStatSize = stat.st_size;
117.}
118.catch (ErrnoException e) {
119.// Do nothing
120.}
121.// Writing was successful, delete the backup file if there is one.
122.mBackupFile.delete(); // 刪除備份文件,標記寫操作成功完成,返回
123.mcr.setDiskWriteResult(true);
124.return;
125.catch (XmlPullParserException e) {
126.Log.w(TAG, "writeToFile: Got exception:", e);
127.catch (IOException e) {
128.Log.w(TAG, "writeToFile: Got exception:", e);
129.}
130.// Clean up an unsuccessfully written file
131.if (mFile.exists()) { // 如果以上寫操作出了任何異常則刪掉(內容)不完整的mFile;放心因爲開始寫之前我們已經備份了,哈哈
132.if (!mFile.delete()) {
133.Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
134.}
135.}
136.mcr.setDiskWriteResult(false); // 標記寫操作以失敗告終
137.}

到現在我們算是明白了mMode和文件權限的關係,爲了更清晰直觀的展現,最後附上ContextImpl.setFilePermissionsFromMode的源碼:

01.static void setFilePermissionsFromMode(String name, int mode,
02.int extraPermissions) {
03.int perms = FileUtils.S_IRUSR|FileUtils.S_IWUSR // 我們可以看出默認創建的文件權限是user自己可讀可寫,
04.|FileUtils.S_IRGRP|FileUtils.S_IWGRP // 同組可讀可寫
05.|extraPermissions; // 和其他附加的,一般給0表示沒附加的權限
06.if ((mode&MODE_WORLD_READABLE) != 0) { // 接下來我們看到只有MODE_WORLD_READABLE/MODE_WORLD_WRITEABLE有用
07.perms |= FileUtils.S_IROTH; // other可讀
08.}
09.if ((mode&MODE_WORLD_WRITEABLE) != 0) {
10.perms |= FileUtils.S_IWOTH; // other可寫
11.}
12.if (DEBUG) {
13.Log.i(TAG, "File " + name + ": mode=0x" + Integer.toHexString(mode)
14.", perms=0x" + Integer.toHexString(perms));
15.}
16.FileUtils.setPermissions(name, perms, -1, -1);
17.}

  通過以上分析我們可以看出每次調用commit()、apply()都會將整個settings全部寫到文件中,即使你只改動了一個setting。因爲它是基於全局的,而不是增量的,所以你的客戶端代碼中一定不要出現一個putXXX就緊跟着一個commit/apply,而是put完所有你要的改動,最後調用一次commit/apply即可。至此Android提供的持久化primitive數據的機制SharedPreferences就已經完全分析完畢了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章