安卓持久化数据存储SharedPreferences

190 阅读25分钟

轻量级的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的包名。关系图如下:

11.png

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()

小结

  1. 初始化SharedPreferencesImpl对象,注意在一个java类中不要多次初始化这个SharedPreferencesImpl对象,因为初始化一次就会开启一个线程load数据,多次初始化,会创建多个线程load数据,会浪费系统资源。
  2. 通过new Thread start方法调用loadFromDisk实现异步加载数据
  3. 数据没有加载完的时候会阻塞sp的其他8种操作,尤其是Editor对象的创建
  4. Sp的数据加载是线程安全的,因为有mLock的wait锁
  5. 加载完的数据缓存到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方法中。 222.png

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/

https://juejin.cn/post/6961961476047568932