Android上常见的数据存储方式为:

  SharedPreferences是 Android 中比较常用的存储方法,本篇将从源码角度带大家分析一下Android中常用的轻量级数据存储工具SharedPreferences。

  1.什么是SharedPreferences?官方说法为:

  它可以用来存储一些比较小的键值对集合;

  对于任何一类的preference,SharedPreferences是唯一的;

  会影响到主线程,造成卡顿,甚至造成anr;

  SharedPreferences不支持多进程;

  2.SharedPreferences常用使用方法:

  1)将数据保存至SharedPreferences

 

/*
  *Context.MODE_PRIVATE: 默认操作模式,代表该文件是私有数据,只能被应用本身访问, 在该模式下,写入
  *的内容会覆盖原文件的内容
  *Context.MODE_APPEND: 该模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件
  *Context.MODE_WORLD_READABLE: 当前文件可以被其他应用读取
  *Context.MODE_WORLD_WRITEABLE:当前文件可以被其他应用写入
  */
  SharedPreferences preferences=getSharedPreferences("user",Context.MODE_PRIVATE);
  Editor editor=preferences.edit();
  String name="测试";
  editor.putString("name", name);
  editor.commit();
  2)从SharedPreferences读取数据
  SharedPreferences preferences=getSharedPreferences("user", Context.MODE_PRIVATE);
  String name=preferences.getString("name", "123");

  3.1 获取getSharedPreferences对象,做了哪些操作?

  以下节选至ContextImpl.getSharedPreferences源码片段:

@Override
  public SharedPreferences getSharedPreferences(String name, int mode) {
  // At least one application in the world actually passes in a null
  // name. This happened to work because when we generated the file name
  // we would stringify it to "null.xml". Nice.
  if (mPackageInfo.getApplicationInfo().targetSdkVersion <
  Build.VERSION_CODES.KITKAT) {
  if (name == null) {
  name = "null";
  }
  }
  File file;
  synchronized (ContextImpl.class) {
  if (mSharedPrefsPaths == null) {
  mSharedPrefsPaths = new ArrayMap<>();
  }
  file = mSharedPrefsPaths.get(name);
  if (file == null) {
  file = getSharedPreferencesPath(name);
  mSharedPrefsPaths.put(name, file);
  }
  }
  return getSharedPreferences(file, mode);
  }
  @Override
  public SharedPreferences getSharedPreferences(File file, int mode) {
  checkMode(mode);
  SharedPreferencesImpl sp;
  synchronized (ContextImpl.class) {
  final ArrayMap cache = getSharedPreferencesCacheLocked();
  sp = cache.get(file);
  if (sp == null) {
  sp = new SharedPreferencesImpl(file, mode);
  cache.put(file, sp);
  return sp;
  }
  }
  if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
  getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
  // If somebody else (some other process) changed the prefs
  // file behind our back, we reload it. This has been the
  // historical (if undocumented) behavior.
  sp.startReloadIfChangedUnexpectedly();
  }
  return sp;
  }

  接着,我们看 sp = new SharedPreferencesImpl(file, mode);

  以下节选至SharedPreferencesImpl源码:

SharedPreferencesImpl(File file, int mode) {
  mFile = file;
  mBackupFile = makeBackupFile(file);
  mMode = mode;
  mLoaded = false;
  mMap = null;
  startLoadFromDisk();
  }
  private void startLoadFromDisk() {
  synchronized (this) {
  mLoaded = false;
  }
  new Thread("SharedPreferencesImpl-load") {
  public void run() {
  loadFromDisk();
  }
  }.start();
  }
  private void loadFromDisk() {
  synchronized (SharedPreferencesImpl.this) {
  if (mLoaded) {
  return;
  }
  if (mBackupFile.exists()) {
  mFile.delete();
  mBackupFile.renameTo(mFile);
  }
  }
  // Debugging
  if (mFile.exists() && !mFile.canRead()) {
  Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
  }
  Map map = null;
  StructStat stat = null;
  try {
  stat = Os.stat(mFile.getPath());
  if (mFile.canRead()) {
  BufferedInputStream str = null;
  try {
  str = new BufferedInputStream(
  new FileInputStream(mFile), 16*1024);
  map = XmlUtils.readMapXml(str);
  } catch (XmlPullParserException | IOException e) {
  Log.w(TAG, "getSharedPreferences", e);
  } finally {
  IoUtils.closeQuietly(str);
  }
  }
  } catch (ErrnoException e) {
  /* ignore */
  }
  synchronized (SharedPreferencesImpl.this) {
  mLoaded = true;
  if (map != null) {
  mMap = map;
  mStatTimestamp = stat.st_mtime;
  mStatSize = stat.st_size;
  } else {
  mMap = new HashMap<>();
  }
  notifyAll();
  }
  }

  由以上可知SharedPreferences的流程为:getSharedPerferences(String,int) ---> getSharedPerferences(File,int) ---> new SharedPerferencesImpl ---> startLoadFromDisk ---> new Thread ---> loadFromDisk ---> notifyAll ---> 返回一个SharedPerferencesImpl对象 ---> 获取SharedPerferences成功

  3.2 putXxx方法解析:

  由以上可知我们的写操作首先需要通过sharedPreferences.edit()方法返回拿到SharedPreferences.Editor,我们知道Editor是一个接口类,所以它的具体实现类是EditorImpl,以下为部分方法片段:

  3.3 getXxx方法解析:

  以getString为例:

@Nullable
  public String getString(String key, @Nullable String defValue) {
  synchronized (this) {
  awaitLoadedLocked();
  String v = (String)mMap.get(key);
  return v != null ? v : defValue;
  }
  }
  private void awaitLoadedLocked() {
  if (!mLoaded) {
  // Raise an explicit StrictMode onReadFromDisk for this
  // thread, since the real read will be in a different
  // thread and otherwise ignored by StrictMode.
  BlockGuard.getThreadPolicy().onReadFromDisk();
  }
  while (!mLoaded) {
  try {
  wait();
  } catch (InterruptedException unused) {
  }
  }
  }

  由以上可知:

  因为使用了synchronize关键字,我们知道getXxx方法是线程安全的

  getXxx方法是直接操作内存的,直接从内存中的mMap中根据传入的key读取value

  3.4 commit方法解析:

  源码片段为:

public boolean commit() {
  // 前面我们分析 putXxx 的时候说过,写操作的记录是存放在 mModified 中的
  // 在这里,commitToMemory() 方法就负责将 mModified 保存的写记录同步到内存中的 mMap 中
  // 并且返回一个 MemoryCommitResult 对象
  MemoryCommitResult mcr = commitToMemory();
  // enqueueDiskWrite 方法负责将数据落地到磁盘上
  SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */);
  try {
  // 同步等待数据落地磁盘工作完成才返回
  mcr.writtenToDiskLatch.await();
  } catch (InterruptedException e) {
  return false;
  }
  // 通知观察者
  notifyListeners(mcr);
  return mcr.writeToDiskResult;
  }

  commit流程为:调用commitToMemory(将mModified同步到mMap) ---> commitToMemory返回 ---> 调用enqueueDiskWrite --->异步任务放入线程池等待调度 ---> enqueueDiskWrite返回 ---> await等待唤醒 ---> 任务被线程池执行 ---> 唤醒await等待;

  3.5 apply() 解析:

public void apply() {
  final MemoryCommitResult mcr = commitToMemory();
  final Runnable awaitCommit = new Runnable() {
  public void run() {
  try {
  mcr.writtenToDiskLatch.await();
  } catch (InterruptedException ignored) {
  }
  }
  };
  QueuedWork.add(awaitCommit);
  Runnable postWriteRunnable = new Runnable() {
  public void run() {
  awaitCommit.run();
  QueuedWork.remove(awaitCommit);
  }
  };
  SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
  // Okay to notify the listeners before it's hit disk
  // because the listeners should always get the same
  // SharedPreferences instance back, which has the
  // changes reflected in memory.
  notifyListeners(mcr);
  }
  // Returns true if any changes were made
  private MemoryCommitResult commitToMemory() {
  MemoryCommitResult mcr = new MemoryCommitResult();
  synchronized (SharedPreferencesImpl.this) {
  // We optimistically don't make a deep copy until
  // a memory commit comes in when we're already
  // writing to disk.
  if (mDiskWritesInFlight > 0) {
  // We can't modify our mMap as a currently
  // in-flight write owns it. Clone it before
  // modifying it.
  // noinspection unchecked
  mMap = new HashMap(mMap);
无锡看男科医院哪家好 https://yyk.familydoctor.com.cn/20612/
  mcr.mapToWriteToDisk = mMap;
  mDiskWritesInFlight++;
  boolean hasListeners = mListeners.size() > 0;
  if (hasListeners) {
  mcr.keysModified = new ArrayList();
  mcr.listeners =
  new HashSet(mListeners.keySet());
  }
  synchronized (this) {
  if (mClear) {
  if (!mMap.isEmpty()) {
  mcr.changesMade = true;
  mMap.clear();
  }
  mClear = false;
  }
  for (Map.Entry e : mModified.entrySet()) {
  String k = e.getKey();
  Object v = e.getValue();
  // "this" is the magic value for a removal mutation. In addition,
  // setting a value to "null" for a given key is specified to be
  // equivalent to calling remove on that key.
  if (v == this || v == null) {
  if (!mMap.containsKey(k)) {
  continue;
  }
  mMap.remove(k);
  } else {
  if (mMap.containsKey(k)) {
  Object existingValue = mMap.get(k);
  if (existingValue != null && existingValue.equals(v)) {
  continue;
  }
  }
  mMap.put(k, v);
  }
  mcr.changesMade = true;
  if (hasListeners) {
  mcr.keysModified.add(k);
  }
  }
  mModified.clear();
  }
  }
  return mcr;
  }

  apply流程为:调用commitToMemory(将mModified同步到mMap) ---> commitToMemory返回 ---> 调用enqueueDiskWrite --->异步任务放入线程池等待调度 ---> enqueueDiskWrite返回 ---> 任务被线程池执行(备份/写入磁盘,清理备份,记录时间/处理失败情况) ---> 任务完成;

  注意:apply与commit的区别为:

  commit()方法是同步的,直接将偏好值(Preference)写入磁盘;而apply()方法是异步的,会先把修改内容提交到SharedPreferences内容缓存中,然后开始异步存储到磁盘;

  commit效率低,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后再进行下一步操作;而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。

  由于apply方法会将等待写入到文件系统的任务放在QueuedWork的等待完成队列里。所以如果我们使用SharedPreference的apply方法, 虽然该方法可以很快返回, 并在其它线程里将键值对写入到文件系统, 但是当Activity的onPause等方法被调用时,会等待写入到文件系统的任务完成,所以如果写入比较慢,主线程就会出现ANR问题。

  commit是在调用线程时就等待写入任务完成,所以不会将等待的时间转嫁到主线程;

  由于在一个进程中,sharedPreference是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。