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

Android数据存储原理分析的更多相关文章

  1. Android数据存储-通过SharedPreferences实现记住密码的操作

    在Android中登陆中,为了实现用户的方便,往往需要根据用户的需要进行记住密码的操作,所以,在Android数据存储中SharedPreferences恰恰可以实现这一点 下面,小编将带领大家通过S ...

  2. 10、Android数据存储

    课程目标: 掌握Android中数据存储的几种方式 熟练使用PreferenceActivity&PreferenceScreen做专业的Setting功能 熟练使用SQLite3来存储数据 ...

  3. 【Android开发日记】之入门篇(八)——Android数据存储(下)

    废话不多说了,紧接着来讲数据库的操作吧.Come On! 提到数据存储问题,数据库是不得不提的.数据库是用来存储关系型数据的不二利器.Android为开发者提供了强大的数据库支持,可以用来轻松地构造基 ...

  4. 重新学习MySQL数据库3:Mysql存储引擎与数据存储原理

    重新学习Mysql数据库3:Mysql存储引擎与数据存储原理 数据库的定义 很多开发者在最开始时其实都对数据库有一个比较模糊的认识,觉得数据库就是一堆数据的集合,但是实际却比这复杂的多,数据库领域中有 ...

  5. Android数据存储之SQLite数据库

    Android数据存储 之SQLite数据库简介 SQLite的相关知识,并结合Java实现对SQLite数据库的操作. SQLite是D.Richard Hipp用C语言编写的开源嵌入式数据库引擎. ...

  6. Android数据存储之SQLCipher数据库加密

    前言: 最近研究了Android Sqlite数据库(文章地址:Android数据存储之Sqlite的介绍及使用)以及ContentProvider程序间数据共享(Android探索之ContentP ...

  7. Android数据存储之GreenDao 3.0 详解

    前言: 今天一大早收到GreenDao 3.0 正式发布的消息,自从2014年接触GreenDao至今,项目中一直使用GreenDao框架处理数据库操作,本人使用数据库路线 Sqlite----> ...

  8. Android数据存储方式--SharedPreferences

    Android数据存储方式有如下四种:SharedPreferences.存储到文件.SQLite数据库.内容提供者(Content provider).存储到网络服务器. 本文主要介绍一下Share ...

  9. Android - 数据存储 -存储文件

    Android使用的文件系统和其他平台的基本磁盘的文件系统很相似.这里将要介绍如何使用File API在Android文件系统中读写文件. File对象适合按顺序读写大量的数据.例如,适合图片文件或者 ...

随机推荐

  1. Spring MVC JSON乱码问题

    之前项目中也遇到过返回JSON时乱码问题,当时找到了一个方法解决了问题但是没有明白原因,今天这个项目又遇到了JSON乱码问题,用之前的方法不行,看了这篇博文才明白为什么 @RequestMapping ...

  2. [web 前端] Npm package.json与package-lock.json文件的作用

    本文链接:https://blog.csdn.net/u013992330/article/details/81110018 最新版nodejs中,多了一个package-lock.json文件,刚开 ...

  3. 【深入学习linux】系统分区与格式化

    分区:把大硬盘分为小的逻辑分区 格式化:写入文件系统 分区设备文件名:给每个分区定义设备文件名 挂载:给每个分区分配挂载点 分区->格式化->取名->分配挂载点(WINDOW下的盘弧 ...

  4. Win10提示“无法打开此计算机上的组策略对象”如何解决

    为了更好地管理电脑,很多朋友都会去编辑Windows10的组策略.不过,有部分用户反馈自己在打开组策略的时候,遇到了“无法打开此计算机上的组策略对象”提示,无法打开组策略,这是怎么回事呢?下面,小编就 ...

  5. [转]Oracle 查询表外键相关信息

    原文地址:https://www.csdn.net/gather_27/MtTaUgxsNzYxMi1ibG9n.html 查找表的外键(包括名称,引用表的表名和对应的键名,下面是分成多步查询): s ...

  6. 将map对象参数转换成String=String&方式

    * 将map对象参数转换成String=String&方式 * @param params * @param charset * @return * @throws UnsupportedEn ...

  7. 安卓 android studio 报错 The specified Android SDK Build Tools version (27.0.3) is ignored, as it is below the minimum supported version (28.0.3) for Android Gradle

    今天将项目迁移到另一台笔记本,进行build出现以下问题,导致build失败 报错截图: 大致意思,目前使用的build工具版本27.0.3不合适.因为当前使用Gradle插件版本是3.2.1,这个版 ...

  8. ---iOS开发 截取字符串中两个指定字符串中间的字符串---

    例如,要截取一个字符串中,两个指定字符串中间的字符串,OC截取方法如下: // 要截取 "> 和 </ 之间的汉字内容: @implementationViewControlle ...

  9. PID:我应该何时计算积分项?

    最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...

  10. android基础---->WebView的使用

    webView的使用 我们通过一个小的测试程序来体会webView的简单使用,项目结构如下: