Android源码分析之SharedPreferences

22 篇文章 1 订阅

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值