文章轉載自: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就已經完全分析完畢了。