SharedPreference 初始化源码解析
作者:海象 发布时间:2023-11-13 07:00:43
初始化
sp 内部将数据放到 xml 文件中,加载时首先会将硬盘中文件读取到内存中,这样加快了访问速度
这次从源码开始,看看里面具体做了什么
// 初始化
SharedPreferencesImpl(File file, int mode) {
// 文件
mFile = file;
//备份文件 .bak 结尾,看看什么时候排上作用,比如恢复数据
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
// 从硬盘中读取
startLoadFromDisk();
}
硬盘中读取文件开了新线程,主要将文件中的内容,转换为Map
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
// 存在备份文件,删除 file,为什么
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
Map<String, Object> map = null;
StructStat stat = null;
Throwable thrown = null;
stat = Os.stat(mFile.getPath());
// 读取流
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
// 转为 map
map = (Map<String, Object>) XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
// 关闭流
IoUtils.closeQuietly(str);
}
}
流程很简单,就是读取硬盘,转换为一个 Map
apply,commit 区别
首先 apply,commit 分别是异步/同步的写入操作,它们都会先写入内存中,也就是更新 Map,不同在于写入到硬盘的时机不同
commit 先看 commit 做了什么 ,commit 方法将返回一个布尔值,表示结果
@Override
public boolean commit() {
// 先提交到内存中
MemoryCommitResult mcr = commitToMemory();
// 执行硬盘中的更新
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
// 提交异常,返回 false
return false;
}
// 通知监听
notifyListeners(mcr);
// 返回结果
return mcr.writeToDiskResult;
}
apply
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
// 都是一样的,先写到内存
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
//
mcr.writtenToDiskLatch.await();
}
};
// 往 sFinishers 队列中添加,等待执行
QueuedWork.addFinisher(awaitCommit);
// 在写完后执行 postWriteRunnable
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
// 执行 awaitCommit
awaitCommit.run();
// sFinishers 队列中移除
QueuedWork.removeFinisher(awaitCommit);
}
};
// 写入硬盘
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
}
硬盘中是如何更新的呢
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);
// 创建 Runnable 对象
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
// 写到文件,这里的锁是 mWritingToDiskLock 对象
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
// 执行 postWriteRunnable, commit 这里为 null
// apply 时不为i而空
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
// 是否为同步提交
// 根据 postWriteRunnable 是否为空, commit 这里为 true
// apply
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
// 放到队列中执行,内部是一个 HandlerThread,按照队列逐个执行任务
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
这里用队列来放任务,应该是要应对多个 commit 情况,这里将所有 commit 往队列里面放,放完后就会执行硬盘的写,apply 也会调用到这里
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
// 添加到 sWork 队列中
sWork.add(work);
// 异步 apply 走这个
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
// 同步 commit 走这个
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
apply 的硬盘写入,需要等待 Activity.onPause() 等时机才会执行
读取
读取比写入就简单很多了
先查看是否从硬盘加载到了内存,没有就先去加载
从内存中读取
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
// 检查是否从硬盘加载到了内存,没有就先去加载
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
如何保证线程安全的
通过 sync 加对象锁,内存读写都是用的同一把锁,所以读写都是线程安全的
数据恢复
存在备份机制
对文件进行写入操作,写入成功时,则将备份文件删除
如果写入失败,之后重新初始化时,就使用备份文件恢复
SP 与 ANR
由于 Activity.onPause 会执行 apply 的数据落盘,里面是有等待锁的,如果时间太长就会 ANR
小结
从 sp 的初始化,读写方面简单分析了流程,sp 有数据恢复机制这是 mmkv 所欠缺的,但在多进程环境下,sp 是不可靠的
相关链接
官方也无力回天?Android SharedPreferences的设计与实现
剖析 SharedPreference apply 引起的 ANR 问题
来源:https://juejin.cn/post/7215851270342541372
猜你喜欢
- 在实际的项目开发过中,当我们修改了某个java类文件时,需要手动重新编译、然后重新启动程序的,整个过程比较麻烦,特别是项目启动慢的时候,更是
- 分栏是报刊、书籍、杂志常用的排版样式,它不仅能方便阅读,同时也能增加页面的美观度。本文将介绍如何在Java应用程序中给Word文档添加多个栏
- 下载和上传附件、发送短信和发送邮件,都算是程序中很常用的功能,之前记录了文件的上传和下载还有发送短信,由于最近比较忙,邮件发送的功能就没有时
- CLR允许将一个对象转换为它的实际类型,或者它的基类型。 在C#中,可将一个对象隐式转换为它的基类型,将对象转换成派生类型需要显示转换。例:
- 本文实例为大家分享了Java实现Flappy Bird游戏的具体代码,供大家参考,具体内容如下1.首先在mainActivity.xml中放
- 本文实例讲述了Android TextView显示Html类解析的网页和图片及自定义标签。分享给大家供大家参考,具体如下:Android系统
- mybatis resulttype 返回值异常在使用mybatis时。resulttype返回自定义的类时,可能返回的类中字段数据存在缺失
- 一、实现MyBatis ID构建接口@Slf4j@Componentpublic class CustomIdGenerator imple
- 前言.如何设置设置使用的地方1.设置类注释模板代码/*** @author: lujie* @create: $date$* @descri
- 说到java中的重载和覆盖呢,大家都很熟悉了吧,但是呢我今天就要写这个。本文主题:一.什么是重载二.什么是覆盖三.两者之间的区别重载(ove
- 本文实例为大家分享了java查找图中两点之间所有路径的具体代码,基于邻接表,供大家参考,具体内容如下图类:package graph1;im
- 详解 Java中日期数据类型的处理之格式转换的实例概要:日期以及时间格式处理,在Java中时间格式一般会涉及到的数据类型包括Calendar
- if语句一个if语句包含一个布尔表达式和一条或多条语句。语法If语句的用语法如下:if(布尔表达式){ //如果布尔
- 1.设计思路,使用VersionCode定义为版本升级参数。android为我们定义版本提供了2个属性:<manifest packa
- 本文实例为大家分享了Java实现串口通信的具体代码,供大家参考,具体内容如下1.介绍使用Java实现的串口通信程序,支持十六进制数据的发送与
- 本文实例讲述了C#读取目录下所有指定类型文件的方法。分享给大家供大家参考。具体分析如下:首先要引入命名空间:using System.IO;
- 二维码是什么二维码 QR Code,全称为:Quick Response Code,最早用于日本汽车制造业追踪零部件。QR现有40个标准版本
- 这篇文章主要介绍了springboot配置文件绑定实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要
- 😎 先看效果一人分饰多角(bushi)😏 后端代码🍗 先引入websocket依赖<!-- websocket消息推送 -->&
- 想用java做一个像windows里一样的txt编辑软件,涉及到字体设置选项卡,在网上找了很久都没找到,就生气啦自己写一个,现在贴这里分享一