zoukankan      html  css  js  c++  java
  • Android数据存储原理分析

      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的。

  • 相关阅读:
    linux 命令——48 watch (转)
    linux 命令——47 iostat (转)
    linux 命令——46 vmstat(转)
    linux 命令——45 free(转)
    linux 命令——44 top (转)
    linux 命令——43 killall(转)
    linux 命令——42 kill (转)
    linux 命令——41 ps(转)
    linux 命令——40 wc (转)
    Java for LeetCode 068 Text Justification
  • 原文地址:https://www.cnblogs.com/djw12333/p/11096616.html
Copyright © 2011-2022 走看看