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

  • 相关阅读:
    watir简单使用
    一些简单的Linux网络配置命令
    Watir识别HTML元素的方法及watir api补充
    web系统测试
    测试方法
    内存泄漏检测工具
    跟我一起学Oracle 11g【10】Oracle 中的那些函数
    限制textarea 文本框的长度(收集了几个办法)
    跟我一起学Oracle 11g【9】SQL 基础学习[嵌套查询]
    通过程序启用/禁用 网络连接(提供4种思路 你值得拥有)
  • 原文地址:https://www.cnblogs.com/djw12333/p/11096616.html
Copyright © 2011-2022 走看看