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的。
Android数据存储原理分析的更多相关文章
- Android数据存储-通过SharedPreferences实现记住密码的操作
在Android中登陆中,为了实现用户的方便,往往需要根据用户的需要进行记住密码的操作,所以,在Android数据存储中SharedPreferences恰恰可以实现这一点 下面,小编将带领大家通过S ...
- 10、Android数据存储
课程目标: 掌握Android中数据存储的几种方式 熟练使用PreferenceActivity&PreferenceScreen做专业的Setting功能 熟练使用SQLite3来存储数据 ...
- 【Android开发日记】之入门篇(八)——Android数据存储(下)
废话不多说了,紧接着来讲数据库的操作吧.Come On! 提到数据存储问题,数据库是不得不提的.数据库是用来存储关系型数据的不二利器.Android为开发者提供了强大的数据库支持,可以用来轻松地构造基 ...
- 重新学习MySQL数据库3:Mysql存储引擎与数据存储原理
重新学习Mysql数据库3:Mysql存储引擎与数据存储原理 数据库的定义 很多开发者在最开始时其实都对数据库有一个比较模糊的认识,觉得数据库就是一堆数据的集合,但是实际却比这复杂的多,数据库领域中有 ...
- Android数据存储之SQLite数据库
Android数据存储 之SQLite数据库简介 SQLite的相关知识,并结合Java实现对SQLite数据库的操作. SQLite是D.Richard Hipp用C语言编写的开源嵌入式数据库引擎. ...
- Android数据存储之SQLCipher数据库加密
前言: 最近研究了Android Sqlite数据库(文章地址:Android数据存储之Sqlite的介绍及使用)以及ContentProvider程序间数据共享(Android探索之ContentP ...
- Android数据存储之GreenDao 3.0 详解
前言: 今天一大早收到GreenDao 3.0 正式发布的消息,自从2014年接触GreenDao至今,项目中一直使用GreenDao框架处理数据库操作,本人使用数据库路线 Sqlite----> ...
- Android数据存储方式--SharedPreferences
Android数据存储方式有如下四种:SharedPreferences.存储到文件.SQLite数据库.内容提供者(Content provider).存储到网络服务器. 本文主要介绍一下Share ...
- Android - 数据存储 -存储文件
Android使用的文件系统和其他平台的基本磁盘的文件系统很相似.这里将要介绍如何使用File API在Android文件系统中读写文件. File对象适合按顺序读写大量的数据.例如,适合图片文件或者 ...
随机推荐
- SOAP简介及实例
SOAP 是基于 XML 的简易协议,可使应用程序在 HTTP 之上进行信息交换. 或者更简单地说:SOAP 是用于访问网络服务的协议. 您应当具备的基础知识 在继续学习之前,您需要对下面的知识有基本 ...
- 005 vue路由
一:元素的获取 1.ref元素获取 可以通过ref获取DOm,也可以获取组件的引用 <!DOCTYPE html> <html lang="en"> < ...
- disruptor 组件理解
disruptor 中核心组件包括 RingBuffer.Event .EventHandler.Sequence.Sequence Barrier. WaitStrategy.WorkProcess ...
- ASP程序加密/解密方法大揭密
如今,用ASP技术构建的网站随处可见.由于ASP脚本是在服务器上解释执行的(无法编译),因此你辛苦开发出来的ASP代码,很容易被人拷去任意修改,如何保护ASP源代码呢?这是每个ASP站长都会遇到的 ...
- echarjs—阿里历年双十一销售数据统计及预测
阿里双十一数据统计 <!DOCTYPE html> <html> <head> <title>阿里历年双十一销售数据统计及预测</title> ...
- URLDoBase64
import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; ...
- python中的一些算法
两个基础知识点:递归和时间复杂度 递归 递归函数的特点:自己调用自己,有结束条件,看下面例子: def fun1(x): """无结束条件,报错""& ...
- PAT 甲级 1076 Forwards on Weibo (30分)(bfs较简单)
1076 Forwards on Weibo (30分) Weibo is known as the Chinese version of Twitter. One user on Weibo m ...
- consul删除无效实例
consul删除无效实例删除无效服务删除无效节点删除无效服务http://127.0.0.1:8500/v1/agent/service/deregister/test-9c14fa595ddfb8f ...
- mysql 5.7 sql_mode设置 坑
原文地址:https://blog.csdn.net/u012259256/article/details/56482218 1.查看sql_mode select @@sql_mode查询出来的值为 ...