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. 第09组 Beta冲刺(4/4)

    队名:软工9组 组长博客:https://www.cnblogs.com/cmlei/ 作业博客:https://edu.cnblogs.com/campus/fzu/SoftwareEngineer ...

  2. Git创建与合并分支,撤销修改

    git回滚到指定版本并推送到远程分支(撤销已提交的修改,并已push) git reset --hard <commit ID号> git push -f git回滚到上一个版本并推送到远 ...

  3. C# System.Web.Mail.MailMessage 发邮件

    C# System.Web.Mail.MailMessage 发邮件 新建控制台Console项目,然后添加 System.Web引用 代码如下: using System; using System ...

  4. java图片处理(加水印、生成缩略图)等之Thumbnailator库

    Thumbnailator 是一个为Java界面更流畅的缩略图生成库.从API提供现有的图像文件和图像对象的缩略图中简化了缩略过程,两三行代码就能够从现有图片生成缩略图,且允许微调缩略图生成,同时保持 ...

  5. 如何在真实串口驱动还未加载的情况下调试uboot?

    1. 先找出真实串口是什么型号 1.1 怎么找?笔者提供两种方案: 方案一: 若当前的板子支持dm,从uboot的dts找串口节点对应的compatible属性 方案二: 从linux内核的dts找串 ...

  6. mysql中的递归

    别人问的一个需求:   数据库里面保存的是父子关系结构: 展示出来的需要根据子类一直查到顶类后将名称全部放一个字段里面,如下:  比如输入的code是1099,名称要显示 配料/花椒/ces     ...

  7. 思维导图Xmind8

    Xmind——一款强大的思维导图工具,方便记忆和理清思路,同时思维导图特殊的记录方式也便于捕捉跳跃思考. 五款思维导图软件:MindManager.iMindMap.XMind.MindMapper. ...

  8. Kubernetes 之 MySQL 持久存储和故障转移(十一)

    目录 一.规划 二.部署 1.创建 PV 和 PVC 2.部署 MySQL 3.更新 MySQL 数据 4.故障转移 一.规划 我们接着之前的文档的架构规划进行下面的操作. IP 角色 192.168 ...

  9. 1206 雅礼集训D2题解

    A \(two\) \(1.1\) \(Description\) 你有两棵有根树,每棵各有 n 个顶点.让我们用整数 1 到 n 给每棵树的顶点编 号.两棵树的根都是顶点 1.第一棵树的边都都是蓝色 ...

  10. TCP/IP学习笔记18--TCP--拥塞控制 (慢开始, 拥塞避免, 快重传和快恢复)

                                                            用最多的梦面对未来   -- 李嘉诚 在某段时间,若对网络资源的需求超过了该资源所能提供 ...