轻量级的SharedPreferences
SharedPreference简称SP是轻量级的存储,SP采用(Key-Value)键值对保存数据的。对多进程不友好,尽量在单进程中使用。保存的数据尽量要少,例如:保存设置菜单中的选项,尽量保证保存的数据量要小。如果需要大量的持久化的数据保存,可以使用#微信自用高性能持久化框架MMKV——基于 mmap的高性能通用 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。有需要的可以参考github地址:MMKV
1、获取SharedPreferences
源码路径:frameworks/base/core/java/android/app/ContextImpl.java
在Activity中调用getSharedPreferences获取SharedPreferences对象。framework代码中都有缓存机制的,不会每次都重新创建的,这就给我们平时写代码带来了设计思想。先从缓存ArrayMap<-sSharedPrefsCache->中通过应用程序的包名获取一个ArrayMap,这个ArrayMap中包含着SP的file和SharedPreferencesImpl一一对应。
203 /**
204 * Map from package name, to preference name, to cached preferences.
205 */
206 @GuardedBy("ContextImpl.class")
207 @UnsupportedAppUsage
208 private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
1.1 getSharedPreferences的实现
565 public SharedPreferences getSharedPreferences(String name, int mode) {
566 // At least one application in the world actually passes in a null
567 // name. This happened to work because when we generated the file name
568 // we would stringify it to "null.xml". Nice.
//在SDK的version小于KITKAT(Android4.4)的版本的时候支持传入的name为null,这时,系统会
//创建一个null.xml的文件,这是谷歌对空的容错处理。
569 if (mPackageInfo.getApplicationInfo().targetSdkVersion <
570 Build.VERSION_CODES.KITKAT) {
571 if (name == null) {
572 name = "null";
573 }
574 }
575
576 File file;
577 synchronized (ContextImpl.class) {
578 if (mSharedPrefsPaths == null) {
579 mSharedPrefsPaths = new ArrayMap<>();
580 }
//先从mSharedPrefsPaths的缓存中获取这个sp的file文件
581 file = mSharedPrefsPaths.get(name);
582 if (file == null) {
583 file = getSharedPreferencesPath(name);【详见1.2小节】
584 mSharedPrefsPaths.put(name, file);
585 }
586 }
587 return getSharedPreferences(file, mode);【详见1.3小节】
588 }
1.2 getSharedPreferencesPath代码的实现
960 @Override
961 public File getSharedPreferencesPath(String name) {
962 return makeFilename(getPreferencesDir(), name + ".xml");
963 }
3566 private File makeFilename(File base, String name) {
3567 if (name.indexOf(File.separatorChar) < 0) {
3568 final File res = new File(base, name);
3569 // We report as filesystem access here to give us the best shot at
3570 // detecting apps that will pass the path down to native code.
3571 BlockGuard.getVmPolicy().onPathAccess(res.getPath());
3572 return res;
3573 }
3574 throw new IllegalArgumentException(
3575 "File " + name + " contains a path separator");
3576 }
731 @UnsupportedAppUsage
732 private File getPreferencesDir() {
733 synchronized (mSync) {
// 如果dir为空,就创建一个shared_prefs的文件夹目录
734 if (mPreferencesDir == null) {
735 mPreferencesDir = new File(getDataDir(), "shared_prefs");
736 }
737 return ensurePrivateDirExists(mPreferencesDir);
738 }
739 }
1.3 getSharedPreferencesPath(File file, int mode)代码的实现
590 @Override
591 public SharedPreferences getSharedPreferences(File file, int mode) {
592 SharedPreferencesImpl sp;
593 synchronized (ContextImpl.class) {//多线程加锁
594 final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
595 sp = cache.get(file);
596 if (sp == null) {
// 检查mode
597 checkMode(mode);
598 if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
599 if (isCredentialProtectedStorage()
600 && !getSystemService(UserManager.class)
601 .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
602 throw new IllegalStateException("SharedPreferences in credential encrypted "
603 + "storage are not available until after user (id "
604 + UserHandle.myUserId() + ") is unlocked");
605 }
606 }
// 创建sp,sp和file绑定在一起【参见2.1小节初始化sp】
607 sp = new SharedPreferencesImpl(file, mode);
608 cache.put(file, sp);
609 return sp;
610 }
611 }
//当设置了多进程或者SDK的version小于3.0,sp会判断上次修改时间或者问价大小是否变化,
//如果变化就重新开启线程load数据【详见1.6小节】
612 if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
613 getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
614 // If somebody else (some other process) changed the prefs
615 // file behind our back, we reload it. This has been the
616 // historical (if undocumented) behavior.
617 sp.startReloadIfChangedUnexpectedly();【参见1.4小节hasFileChangedUnexpectedly】
618 }
619 return sp;
620 }
1.4 startReloadIfChangedUnexpectedly方法的实现
/frameworks/base/core/java/android/app/SharedPreferencesImpl.java
216 @UnsupportedAppUsage
217 void startReloadIfChangedUnexpectedly() {
218 synchronized (mLock) {
219 // TODO: wait for any pending writes to disk?
220 if (!hasFileChangedUnexpectedly()) {
221 return;
222 }
// 从磁盘中异步加载数据
223 startLoadFromDisk();
224 }
225 }
226
227 // Has the file changed out from under us? i.e. writes that
228 // we didn't instigate.
229 private boolean hasFileChangedUnexpectedly() {
230 synchronized (mLock) {
//mDiskWritesInFlight > 0 表示当前数据正在写入缓存中
231 if (mDiskWritesInFlight > 0) {
232 // If we know we caused it, it's not unexpected.
233 if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
234 return false;
235 }
236 }
237
238 final StructStat stat;
239 try {
240 /*
241 * Metadata operations don't usually count as a block guard
242 * violation, but we explicitly want this one.
243 */
244 BlockGuard.getThreadPolicy().onReadFromDisk();
245 stat = Os.stat(mFile.getPath());
246 } catch (ErrnoException e) {
247 return true;
248 }
249
250 synchronized (mLock) {
// 判断两个StructStat的时间戳是否一致,或者大小是否一致
// 如果时间戳或者大小中有一个不相等,就重新load数据。
251 return !stat.st_mtim.equals(mStatTimestamp) || mStatSize != stat.st_size;
252 }
253 }
1.5 checkMode 不能申请MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE
3536 private void checkMode(int mode) {
3537 if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
3538 if ((mode & MODE_WORLD_READABLE) != 0) {
3539 throw new SecurityException("MODE_WORLD_READABLE no longer supported");
3540 }
3541 if ((mode & MODE_WORLD_WRITEABLE) != 0) {
3542 throw new SecurityException("MODE_WORLD_WRITEABLE no longer supported");
3543 }
3544 }
3545 }
1.6 MODE_MULTI_PROCESS 说明
谷歌已经把这个属性Deprecated,并且强烈建议app不使用这个mode,“Applications should not attempt to use it.”。 如果是跨进程,建议使用ContentProivder。
/frameworks/base/core/java/android/content/Context.java
33 /**
234 * SharedPreference loading flag: when set, the file on disk will
235 * be checked for modification even if the shared preferences
236 * instance is already loaded in this process. This behavior is
237 * sometimes desired in cases where the application has multiple
238 * processes, all writing to the same SharedPreferences file.
239 * Generally there are better forms of communication between
240 * processes, though.
241 *
242 * <p>This was the legacy (but undocumented) behavior in and
243 * before Gingerbread (Android 2.3) and this flag is implied when
244 * targeting such releases. For applications targeting SDK
245 * versions <em>greater than</em> Android 2.3, this flag must be
246 * explicitly set if desired.
247 *
248 * @see #getSharedPreferences
249 *
250 * @deprecated MODE_MULTI_PROCESS does not work reliably in
251 * some versions of Android, and furthermore does not provide any
252 * mechanism for reconciling concurrent modifications across
253 * processes. **Applications should not attempt to use it.** Instead,
254 * they should use an explicit cross-process data management
255 * approach such as { @link android.content.ContentProvider ContentProvider}.
256 */
257 @Deprecated
258 public static final int MODE_MULTI_PROCESS = 0x0004;
小结
- SP不支持多进程,仅仅支持单进程;如果需要跨进程的持久化的数据存储,谷歌建议用ContentProvider。
- SP文件是保存在当前apk的路径下:/data/data/package_name/shared_prefs/
- SP的mode不能申请MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,只能用MODE_PRIVATE。
- 一个sp文件对应一个SharedPreferencesImpl类,他们的对应关系缓存在sSharedPrefsCache的value中,这个也是一个ArrayMap,表示一个app可以有多个sp文件,sSharedPrefsCache的key是app的包名。关系图如下:
2.SharedPreferencesImpl.java
2.1 SharedPreferencesImpl构造函数
122 @UnsupportedAppUsage
123 SharedPreferencesImpl(File file, int mode) {
124 mFile = file;
// 创建.bak的备份文件,
125 mBackupFile = makeBackupFile(file);
126 mMode = mode;
127 mLoaded = false;
128 mMap = null;
129 mThrowable = null;
// 异步加载文件
130 startLoadFromDisk();
131 }
2.2 startLoadFromDisk
开启一个名为SharedPreferencesImpl-load的线程,异步加载数据
133 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
134 private void startLoadFromDisk() {
135 synchronized (mLock) {
136 mLoaded = false;
137 }
138 new Thread("SharedPreferencesImpl-load") {
139 public void run() {
140 loadFromDisk();
141 }
142 }.start();
143 }
2.3 loadFromDisk
145 private void loadFromDisk() {
146 synchronized (mLock) {
147 if (mLoaded) {
148 return;
149 }
// 如果mBackupFile存在,删除mFile,把mBackupFile重命名为mFile
150 if (mBackupFile.exists()) {
151 mFile.delete();
152 mBackupFile.renameTo(mFile);
153 }
154 }
155
156 // Debugging
157 if (mFile.exists() && !mFile.canRead()) {
158 Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
159 }
160
161 Map<String, Object> map = null;
162 StructStat stat = null;
163 Throwable thrown = null;
164 try {
165 stat = Os.stat(mFile.getPath());
166 if (mFile.canRead()) {
167 BufferedInputStream str = null;
168 try {
169 str = new BufferedInputStream(
170 new FileInputStream(mFile), 16 * 1024);
// 通过XmlUtils解析xml文件,把解析出来的数据缓存到map中
171 map = (Map<String, Object>) XmlUtils.readMapXml(str);
172 } catch (Exception e) {
173 Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
174 } finally {
175 IoUtils.closeQuietly(str);
176 }
177 }
178 } catch (ErrnoException e) {
179 // An errno exception means the stat failed. Treat as empty/non-existing by
180 // ignoring.
181 } catch (Throwable t) {
182 thrown = t;
183 }
184
185 synchronized (mLock) {
186 mLoaded = true;
187 mThrowable = thrown;
188
189 // It's important that we always signal waiters, even if we'll make
190 // them fail with an exception. The try-finally is pretty wide, but
191 // better safe than sorry.
192 try {
193 if (thrown == null) {
194 if (map != null) {
195 mMap = map;
// 更新时间戳和size大小,为了后续是否重新加载的判断用
196 mStatTimestamp = stat.st_mtim;
197 mStatSize = stat.st_size;
198 } else {
199 mMap = new HashMap<>();
200 }
201 }
202 // In case of a thrown exception, we retain the old map. That allows
203 // any open editors to commit and store updates.
204 } catch (Throwable t) {
205 mThrowable = t;//保存异常,为了后续抛出这个异常
206 } finally {
// 数据加载完了,唤醒等待mLock的线程
207 mLock.notifyAll();
208 }
209 }
210 }
2.4 getString 获取key的值
297 @Override
298 @Nullable
299 public String getString(String key, @Nullable String defValue) {
300 synchronized (mLock) {
301 awaitLoadedLocked();//等待是否加载完数据,这个里面有个mLock的锁
// 从缓存中读取key值,这是从HashMap中取key值,
302 String v = (String)mMap.get(key);
// 如果key的值为Null,就取默认值。
303 return v != null ? v : defValue;
304 }
305 }
2.5 awaitLoadedLocked
269 @GuardedBy("mLock")
270 private void awaitLoadedLocked() {
271 if (!mLoaded) {
272 // Raise an explicit StrictMode onReadFromDisk for this
273 // thread, since the real read will be in a different
274 // thread and otherwise ignored by StrictMode.
275 BlockGuard.getThreadPolicy().onReadFromDisk();
276 }
// 当正在load数据时,mLoaded的值为false,当数据完全加载到map后,mLoaded值为true
277 while (!mLoaded) {
278 try {
279 mLock.wait();//添加mLock的wait锁,等待mLock.notifyAll释放锁
280 } catch (InterruptedException unused) {
281 }
282 }
// 前面【2.3小节】保存的异常,在mLock的wait锁释放后抛出这个异常
283 if (mThrowable != null) {
284 throw new IllegalStateException(mThrowable);
285 }
286 }
awaitLoadedLocked的操作有8个,读取key的操作,和new Editor的操作:
- public String getString(String key, @Nullable String defValue)
- public Map<String, ?> getAll()
- public Set getStringSet(String key, @Nullable Set defValues)
- public int getInt(String key, int defValue)
- public long getLong(String key, long defValue)
- public float getFloat(String key, float defValue)
- public boolean contains(String key)
- public Editor edit()
小结
- 初始化SharedPreferencesImpl对象,注意在一个java类中不要多次初始化这个SharedPreferencesImpl对象,因为初始化一次就会开启一个线程load数据,多次初始化,会创建多个线程load数据,会浪费系统资源。
- 通过new Thread start方法调用loadFromDisk实现异步加载数据
- 数据没有加载完的时候会阻塞sp的其他8种操作,尤其是Editor对象的创建
- Sp的数据加载是线程安全的,因为有mLock的wait锁
- 加载完的数据缓存到mMap中,mMap是HashMap,以后就是对mMap进行操作,查询的时候也是通过mMap来操作的。
3.EditorImpl
3.1 EditorImpl的初始化
EditorImpl是通过调用edit()来实现初始化的,所以在我们的代码中应该尽量避免多次调用edit,避免创建不必要的堆内存。该过程同样要等待awaitLoadedLocked完成, 然后创建EditorImpl对象. EditorImpl作为SharedPreferencesImpl的内部类,其继承于Editor接口.
358 @Override
359 public Editor edit() {
360 // TODO: remove the need to call awaitLoadedLocked() when
361 // requesting an editor. will require some work on the
362 // Editor, but then we should be able to do:
363 //
364 // context.getSharedPreferences(..).edit().putString(..).apply()
365 //
366 // ... all without blocking.
367 synchronized (mLock) {
368 awaitLoadedLocked();
369 }
370
371 return new EditorImpl();
372 }
3.2 EditorImpl内部类,继承于Editor接口
405 public final class EditorImpl implements Editor {
406 private final Object mEditorLock = new Object();
407
408 @GuardedBy("mEditorLock")
409 private final Map<String, Object> mModified = new HashMap<>();
410
411 @GuardedBy("mEditorLock")
412 private boolean mClear = false;
414 @Override
415 public Editor putString(String key, @Nullable String value) {
416 synchronized (mEditorLock) {
// 插入数据,先把数据缓存在mModified的HashMap中
417 mModified.put(key, value);
418 return this;
419 }
420 }
... ...
458 @Override
459 public Editor remove(String key) {
460 synchronized (mEditorLock) {
// 移除数据,后面会判断这个this,如果是this,就移除这个对象,这里没有真正删除数据,而是在commitToMemory的时候真正操作删除数据
461 mModified.put(key, this);
462 return this;
463 }
464 }
465
466 @Override //清除数据
467 public Editor clear() {
468 synchronized (mEditorLock) {
469 mClear = true;//设置清除标志位
470 return this;
471 }
472 }
... ...
3.3 commit提交数据
594 @Override
595 public boolean commit() {
596 long startTime = 0;//debug用,计算commit耗时用
597
598 if (DEBUG) {
599 startTime = System.currentTimeMillis();
600 }
601 // 把数据更改更新到缓存中,mMap的数据更新
602 MemoryCommitResult mcr = commitToMemory();//【参见3.3小节】
603 // 把更改的缓存数据mcr排队写入磁盘【参见3.4小节】
604 SharedPreferencesImpl.this.enqueueDiskWrite(
605 mcr, null /* sync write on this thread okay */);
606 try {
// 这里等待递减锁的状态为0,就释放锁。
// CountDownLatch.countDown()递减锁的状态减一
// 文件写完就释放这个锁
607 mcr.writtenToDiskLatch.await();
608 } catch (InterruptedException e) {
609 return false;
610 } finally {
611 if (DEBUG) {
612 Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
613 + " committed after " + (System.currentTimeMillis() - startTime)
614 + " ms");
615 }
616 }
// 通知哪些缓存中的数据发生变化,并在主线程回调onSharedPreferenceChanged()方法
617 notifyListeners(mcr);
618 return mcr.writeToDiskResult;
619 }
3.3 commitToMemory
514 // Returns true if any changes were made
515 private MemoryCommitResult commitToMemory() {
516 long memoryStateGeneration;
517 boolean keysCleared = false;
518 List<String> keysModified = null;
519 Set<OnSharedPreferenceChangeListener> listeners = null;
520 Map<String, Object> mapToWriteToDisk;//要写入磁盘的数据
521
522 synchronized (SharedPreferencesImpl.this.mLock) {
523 // We optimistically don't make a deep copy until
524 // a memory commit comes in when we're already
525 // writing to disk.
526 if (mDiskWritesInFlight > 0) {
527 // We can't modify our mMap as a currently
528 // in-flight write owns it. Clone it before
529 // modifying it.
530 // noinspection unchecked
531 mMap = new HashMap<String, Object>(mMap);
532 }
//浅拷贝,把对象的引用地址复制给mapToWriteToDisk
//修改mapToWriteToDisk,mMap的值同样也会修改
533 mapToWriteToDisk = mMap;
534 mDiskWritesInFlight++;//给line526行判断用
535 // 判断是否注册了观察者
536 boolean hasListeners = mListeners.size() > 0;
537 if (hasListeners) {
538 keysModified = new ArrayList<String>();
539 listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
540 }
541
542 synchronized (mEditorLock) {
543 boolean changesMade = false;
544 // 如果在【3.2小节】中调用了clear()方法,mClear==true
545 if (mClear) {
546 if (!mapToWriteToDisk.isEmpty()) {
547 changesMade = true;
// Removes all of the mappings from this map
548 mapToWriteToDisk.clear();//清除hashmap的的mapping
549 }
550 keysCleared = true;
551 mClear = false;
552 }
553 // 遍历mModified hashmap,editor执行put/remove的操作操作mModified的值的改变
554 for (Map.Entry<String, Object> e : mModified.entrySet()) {
555 String k = e.getKey();
556 Object v = e.getValue();
557 // "this" is the magic value for a removal mutation. In addition,
558 // setting a value to "null" for a given key is specified to be
559 // equivalent to calling remove on that key.
// 调用editor的remove方法,v被赋值this或者v是null时,表示要删除这个key值
560 if (v == this || v == null) {
561 if (!mapToWriteToDisk.containsKey(k)) {
562 continue;
563 }
// 删除mapToWriteToDisk的key的值
564 mapToWriteToDisk.remove(k);
565 } else {
// 校验key值是否存在mapToWriteToDisk的map中
566 if (mapToWriteToDisk.containsKey(k)) {
567 Object existingValue = mapToWriteToDisk.get(k);
// 如果key获取的值不为空,并且和要修改的值不一致的时候,才会真正更新值。
568 if (existingValue != null && existingValue.equals(v)) {
569 continue;
570 }
571 }
// 更新mModified中key的值到mMap中
572 mapToWriteToDisk.put(k, v);
573 }
574
575 changesMade = true;
// 如果注册了观察者,这时会把要修改的key的值保存到list keysModified中
576 if (hasListeners) {
577 keysModified.add(k);
578 }
579 }
580 // mModified使用结束,清空mModified的key-value值
581 mModified.clear();
582
583 if (changesMade) {
584 mCurrentMemoryStateGeneration++;
585 }
586
587 memoryStateGeneration = mCurrentMemoryStateGeneration;
588 }
589 }
// 构造MemoryCommitResult对象,给相应的属性赋值
590 return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
591 listeners, mapToWriteToDisk);
592 }
小结:
- 首先更新缓存数据,通过commitToMemory把需要更新的数据更新到mapToWriteToDisk中,也同步更新到mMap
- enqueueDiskWrite把缓存数据排队写入磁盘,传递的runnable是null,表示不需要异步执行。
- commitToMemory方法中
- mapToWriteToDisk = mMap;浅拷贝,把对象的引用地址复制给mapToWriteToDisk
- 先判断mClear的值是否为true,如果为true,清除mapToWriteToDisk中的键值对
- 遍历mModified,查看哪些key的value发生了改变
- 如果value的值是this或者Null,从mapToWriteToDisk删除key值
- 判断mModified的value的值是否和mapToWriteToDisk的值一致,不一致就更新
- 数据更新完,把mModified的key-value的键值对清空
- 构造MemoryCommitResult对象
3.4 enqueueDiskWrite 把要修改的数据排队写入磁盘中
664 private void enqueueDiskWrite(final MemoryCommitResult mcr,
665 final Runnable postWriteRunnable) {
//【根据3.3小节】postWriteRunnable的值是null,所以isFromSyncCommit为true
666 final boolean isFromSyncCommit = (postWriteRunnable == null);
667 // new一个写入磁盘的Runnable线程
668 final Runnable writeToDiskRunnable = new Runnable() {
669 @Override
670 public void run() {
671 synchronized (mWritingToDiskLock) {
// 开始写文件操作
672 writeToFile(mcr, isFromSyncCommit);
673 }
674 synchronized (mLock) {
//【1.4小节和3.3小节判断用到这个值】
675 mDiskWritesInFlight--;
676 }
677 if (postWriteRunnable != null) {
678 postWriteRunnable.run();
679 }
680 }
681 };
682
683 // Typical #commit() path with fewer allocations, doing a write on
684 // the current thread.
// 当调用commit提交数据写入磁盘的时候,这个isFromSyncCommit为true
685 if (isFromSyncCommit) {
686 boolean wasEmpty = false;
687 synchronized (mLock) {
688 wasEmpty = mDiskWritesInFlight == 1;
689 }
// 在当前线程中执行writeToDiskRunnable方法
690 if (wasEmpty) {
691 writeToDiskRunnable.run();
692 return;
693 }
694 }
695 // commit方法不会走到这个方法,因为上面已经return了
696 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
697 }
小结:
- 先new一个writeToDiskRunnable的runnable,调用写磁盘操作
- 判断wasEmpty的值,此时值为true
- 在当前线程执行writeToDiskRunnable的run方法
3.5 writeToFile
722 @GuardedBy("mWritingToDiskLock")
723 private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
724 long startTime = 0;
725 long existsTime = 0;
726 long backupExistsTime = 0;
727 long outputStreamCreateTime = 0;
728 long writeTime = 0;
729 long fsyncTime = 0;
730 long setPermTime = 0;
731 long fstatTime = 0;
732 long deleteTime = 0;
733
734 if (DEBUG) {
735 startTime = System.currentTimeMillis();
736 }
737
738 boolean fileExists = mFile.exists();
739
740 if (DEBUG) {
741 existsTime = System.currentTimeMillis();
742
743 // Might not be set, hence init them to a default value
744 backupExistsTime = existsTime;
745 }
746
747 // Rename the current file so it may be used as a backup during the next read
// 重命名当前文件,以便在下次读取时将其用作备份
748 if (fileExists) {
749 boolean needsWrite = false;
750
751 // Only need to write if the disk state is older than this commit
752 if (mDiskStateGeneration < mcr.memoryStateGeneration) {
753 if (isFromSyncCommit) { //commit的时候isFromSyncCommit为true
754 needsWrite = true;
755 } else {
756 synchronized (mLock) {
757 // No need to persist intermediate states. Just wait for the latest state to
758 // be persisted.
759 if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
760 needsWrite = true;
761 }
762 }
763 }
764 }
765
766 if (!needsWrite) {//不需要写入磁盘,直接返回
767 mcr.setDiskWriteResult(false, true);
768 return;
769 }
770 // 判断backup文件是否存在
771 boolean backupFileExists = mBackupFile.exists();
772
773 if (DEBUG) {
774 backupExistsTime = System.currentTimeMillis();
775 }
776
777 if (!backupFileExists) {//backup文件不存在,mfile 重命名为backupFile
778 if (!mFile.renameTo(mBackupFile)) {
779 Log.e(TAG, "Couldn't rename file " + mFile
780 + " to backup file " + mBackupFile);
781 mcr.setDiskWriteResult(false, false);
782 return;
783 }
784 } else { // backup文件存在,删除mFile文件
785 mFile.delete();
786 }
787 }
788
789 // Attempt to write the file, delete the backup and return true as atomically as
790 // possible. If any exception occurs, delete the new file; next time we will restore
791 // from the backup.
792 try {
793 FileOutputStream str = createFileOutputStream(mFile);
794
795 if (DEBUG) {
796 outputStreamCreateTime = System.currentTimeMillis();
797 }
798
799 if (str == null) {//返回,不写入磁盘的操作
800 mcr.setDiskWriteResult(false, false);
801 return;
802 }
// 将mapToWriteToDisk中的数据写入xml文件
803 XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
804
805 writeTime = System.currentTimeMillis();
806
807 FileUtils.sync(str);
808
809 fsyncTime = System.currentTimeMillis();
810
811 str.close();//关闭FileOutputStream
812 ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
813
814 if (DEBUG) {
815 setPermTime = System.currentTimeMillis();
816 }
817
818 try {
819 final StructStat stat = Os.stat(mFile.getPath());
820 synchronized (mLock) {
821 mStatTimestamp = stat.st_mtim;//更新timestamp时间戳
822 mStatSize = stat.st_size;//更新文件大小
823 }
824 } catch (ErrnoException e) {
825 // Do nothing
826 }
827
828 if (DEBUG) {
829 fstatTime = System.currentTimeMillis();
830 }
831
832 // Writing was successful, delete the backup file if there is one.
833 mBackupFile.delete();//写入文件成功,删除备份文件
834
835 if (DEBUG) {
836 deleteTime = System.currentTimeMillis();
837 }
838
839 mDiskStateGeneration = mcr.memoryStateGeneration;
840 // 设置写入磁盘成功的状态标志位,同时释放writtenToDiskLatch锁
// 第二个true是commit的返回值,true表示写入成功,false表示失败
// 第一个true 调用apply()debug用
841 mcr.setDiskWriteResult(true, true);
842
843 if (DEBUG) {
844 Log.d(TAG, "write: " + (existsTime - startTime) + "/"
845 + (backupExistsTime - startTime) + "/"
846 + (outputStreamCreateTime - startTime) + "/"
847 + (writeTime - startTime) + "/"
848 + (fsyncTime - startTime) + "/"
849 + (setPermTime - startTime) + "/"
850 + (fstatTime - startTime) + "/"
851 + (deleteTime - startTime));
852 }
853
854 long fsyncDuration = fsyncTime - writeTime;
855 mSyncTimes.add((int) fsyncDuration);
856 mNumSync++;
857
858 if (DEBUG || mNumSync % 1024 == 0 || fsyncDuration > MAX_FSYNC_DURATION_MILLIS) {
859 mSyncTimes.log(TAG, "Time required to fsync " + mFile + ": ");
860 }
861
862 return;
863 } catch (XmlPullParserException e) {
864 Log.w(TAG, "writeToFile: Got exception:", e);
865 } catch (IOException e) {
866 Log.w(TAG, "writeToFile: Got exception:", e);
867 }
868
869 // Clean up an unsuccessfully written file
870 if (mFile.exists()) {//清除没有写成功的文件
871 if (!mFile.delete()) {
872 Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
873 }
874 }
// 抛出异常,表示写入失败了,第二个false返回给commit()
875 mcr.setDiskWriteResult(false, false);
876 }
小结
- 根据isFromSyncCommit的值判断是否要写入文件,commit传递过来的值为true,所以要写入文件。
- 将mapToWriteToDisk也就是mMap全部key-value键值对写入文件, 如果写入成功则删除备份文件,如果写入失败则删除mFile.
- Commit的时候,就是更新所有mMap的键值对写入文件,所以xml文件中的条目要尽可能的少,不能太多,避免写操作耗时。
3.6 apply 异步写入操作
474 @Override
475 public void apply() {
476 final long startTime = System.currentTimeMillis();
477
478 final MemoryCommitResult mcr = commitToMemory();//【参见3.3小节】
479 final Runnable awaitCommit = new Runnable() {
480 @Override
481 public void run() {
482 try {
483 mcr.writtenToDiskLatch.await();
484 } catch (InterruptedException ignored) {
485 }
486
487 if (DEBUG && mcr.wasWritten) {
488 Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
489 + " applied after " + (System.currentTimeMillis() - startTime)
490 + " ms");
491 }
492 }
493 };
494 // 在QueuedWork中添加writtenToDiskLatch等待锁,等待异步写入成功
495 QueuedWork.addFinisher(awaitCommit);
496
497 Runnable postWriteRunnable = new Runnable() {
498 @Override
499 public void run() {
500 awaitCommit.run();
501 QueuedWork.removeFinisher(awaitCommit);
502 }
503 };
504 // 传入postWriteRunnable线程,commit的时候传入的是空的。
505 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
506
507 // Okay to notify the listeners before it's hit disk
508 // because the listeners should always get the same
509 // SharedPreferences instance back, which has the
510 // changes reflected in memory.
511 notifyListeners(mcr);
512 }
3.7 enqueueDiskWrite
648 /**
649 * Enqueue an already-committed-to-memory result to be written
650 * to disk.
651 *
652 * They will be written to disk one-at-a-time in the order
653 * that they're enqueued.
654 *
655 * @param postWriteRunnable if non-null, we're being called
656 * from apply() and this is the runnable to run after
657 * the write proceeds. if null (from a regular commit()),
658 * then we're allowed to do this disk write on the main
659 * thread (which in addition to reducing allocations and
660 * creating a background thread, this has the advantage that
661 * we catch them in userdebug StrictMode reports to convert
662 * them where possible to apply() ...)
663 */
664 private void enqueueDiskWrite(final MemoryCommitResult mcr,
665 final Runnable postWriteRunnable) {
// apply调用这个函数的时候,这个postWriteRunnable不为空
666 final boolean isFromSyncCommit = (postWriteRunnable == null);
667
668 final Runnable writeToDiskRunnable = new Runnable() {
669 @Override
670 public void run() {
671 synchronized (mWritingToDiskLock) {
//开始写文件,【参见3.5小节】
672 writeToFile(mcr, isFromSyncCommit);
673 }
674 synchronized (mLock) {
675 mDiskWritesInFlight--;
676 }
677 if (postWriteRunnable != null) {
678 postWriteRunnable.run();
679 }
680 }
681 };
682
683 // Typical #commit() path with fewer allocations, doing a write on
684 // the current thread.
685 if (isFromSyncCommit) {//apply方法的时候isFromSyncCommit为false
686 boolean wasEmpty = false;
687 synchronized (mLock) {
688 wasEmpty = mDiskWritesInFlight == 1;
689 }
690 if (wasEmpty) {
691 writeToDiskRunnable.run();
692 return;
693 }
694 }
695 // 调用QueuedWork的queue方法,异步启动线程去执行
696 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
697 }
3.8 QueuedWork.queue
215 /**
216 * Queue a work-runnable for processing asynchronously.
217 *
218 * @param work The new runnable to process
219 * @param shouldDelay If the message should be delayed
220 */
221 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
222 public static void queue(Runnable work, boolean shouldDelay) {
223 Handler handler = getHandler();
224
225 synchronized (sLock) {
226 sWork.add(work);
227 // 当没有主线程等待的时候,这个会有100ms的延时
// 当有主线程在等待的时候,sCanDelay为true,立刻执行
228 if (shouldDelay && sCanDelay) {
229 handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
230 } else {
231 handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
232 }
233 }
234 }
这是waitToFinish的方法的调用地方,在Activity的onPause/onStop方法中,在Service的onStop方法中。
147 /**
148 * Trigger queued work to be processed immediately. The queued work is processed on a separate
149 * thread asynchronous. While doing that run and process all finishers on this thread. The
150 * finishers can be implemented in a way to check weather the queued work is finished.
151 *
152 * Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive,
153 * after Service command handling, etc. (so async work is never lost)
154 */
155 public static void waitToFinish() {
156 long startTime = System.currentTimeMillis();
157 boolean hadMessages = false;
158
159 Handler handler = getHandler();
160
161 synchronized (sLock) {
162 if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
163 // Delayed work will be processed at processPendingWork() below
164 handler.removeMessages(QueuedWorkHandler.MSG_RUN);
165
166 if (DEBUG) {
167 hadMessages = true;
168 Log.d(LOG_TAG, "waiting");
169 }
170 }
171
172 // We should not delay any work as this might delay the finishers
173 sCanDelay = false;
174 }
175
176 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
177 try {
178 processPendingWork();//遍历sWork执行
179 } finally {
180 StrictMode.setThreadPolicy(oldPolicy);
181 }
182
183 try {
184 while (true) {
185 Runnable finisher;
186
187 synchronized (sLock) {// 取出要执行的runnable
188 finisher = sFinishers.poll();
189 }
190
191 if (finisher == null) {
192 break;
193 }
194
195 finisher.run();//执行runnable的run方法
196 }
197 } finally {
198 sCanDelay = true;
199 }
200
201 synchronized (sLock) {
202 long waitTime = System.currentTimeMillis() - startTime;
203
204 if (waitTime > 0 || hadMessages) {
205 mWaitTimes.add(Long.valueOf(waitTime).intValue());
206 mNumWaits++;
207
208 if (DEBUG || mNumWaits % 1024 == 0 || waitTime > MAX_WAIT_TIME_MILLIS) {
209 mWaitTimes.log(LOG_TAG, "waited: ");
210 }
211 }
212 }
213 }
276 private static class QueuedWorkHandler extends Handler {
277 static final int MSG_RUN = 1;
278
279 QueuedWorkHandler(Looper looper) {
280 super(looper);
281 }
282
283 public void handleMessage(Message msg) {
284 if (msg.what == MSG_RUN) {//MSG_RUN消息
285 processPendingWork(); //真正开始执行pendingWork
286 }
287 }
288 }
245 private static void processPendingWork() {
246 long startTime = 0;
247
248 if (DEBUG) {
249 startTime = System.currentTimeMillis();
250 }
251
252 synchronized (sProcessingWork) {
253 LinkedList<Runnable> work;
254
255 synchronized (sLock) {
256 work = sWork;
257 sWork = new LinkedList<>();
258
259 // Remove all msg-s as all work will be processed now
260 getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
261 }
262
263 if (work.size() > 0) {
264 for (Runnable w : work) {//遍历work,异步执行run方法
265 w.run();
266 }
267
268 if (DEBUG) {
269 Log.d(LOG_TAG, "processing " + work.size() + " items took " +
270 +(System.currentTimeMillis() - startTime) + " ms");
271 }
272 }
273 }
274 }
小结:
- apply异步写入磁盘,首先把awaitCommit线程放到QueuedWork中
- 构建postWriteRunnable线程,里面执行awaitCommit的run方法,移除awaitCommit在QueuedWord中
- 在enqueueDiskWrite方法中创建writeToDiskRunnable线程,writeToDiskRunnable的run方法中执行传递过来的postWriteRunnable的run方法
- 把writeToDiskRunnable线程交给QueuedWord的queue方法执行
- QueuedWord发送消息MSG_RUN执行,调用processPendingWork开始执行
- 如果没有主线程的方法调用waitToFinish,则延迟100ms执行MSG_RUN消息;如果有主线程的方法调用waitToFinish,则立刻执行MSG_RUN消息
注意: 在调用apply方法的时候是异步执行的,当Activity切换退出的时候,执行onPause回调的时候,这个时候如果sp文件的写入操作还没有执行完,就会阻塞Activity的onPause方法。所以sp只是轻量级的文件,只适合保存少量开关设置项的case。
dCommit有返回值,返回值就是是否写入成功。apply没有返回值,和commit的区别就是开启线程异步讲mMap的键值对写入xml中。其中有一个100ms的延迟的设置。对于频繁修改xml,我们app也可以内部做一个缓存数据,当在退出activit的时候一次性提交修改内容。
温馨提示:如有纰漏,欢迎各位同学指正。 参考链接: https://gityuan.com/2017/06/18/SharedPreferences/