文章转载自: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就已经完全分析完毕了。