Android SharedPreferences源码分析

文章目录

  • Android SharedPreferences源码分析
    • 概述
    • 基本使用
    • 源码分析
      • 获取SP对象
      • 初始化和读取数据
      • 写入数据
        • MemoryCommitResult
        • commitToMemory()
        • commit()
        • apply()
        • enqueueDiskWrite()
        • writeToFile()
      • 主动等待写回任务结束
    • 总结

Android SharedPreferences源码分析

概述

SharedPreferences 是 Android 平台上轻量级的 K-V 存储框架。

SharedPreferences 采用 XML 文件格式持久化键值对数据,文件的存储位置位于应用沙盒的内部存储 /data/data/<包名>/shared_prefs/ 位置,每个 XML 文件对应于一个 SharedPreferences 对象。

一个sp文件(XML文件) 对应一个SharedPreferences对象。

基本使用

SharedPreferences sp = context.getSharedPreferences("app", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putString("name", "小明").putInt("age", 18).putBoolean("sex", true).apply();String name = sp.getString("name", "");
int age = sp.getInt("age", 0);
boolean sex = sp.getBoolean("sex", false);

查看 /data/data/com.example.myapplication/shared_prefs/app.xml 文件:

在这里插入图片描述

源码分析

获取SP对象

ContextImpl 类是 Context 抽象类的实现类。

// ContextImpl.javaclass ContextImpl extends Context {// sp文件的根目录private File mPreferencesDir;// 缓存name和file对象的对应关系private ArrayMap<String, File> mSharedPrefsPaths;// 缓存包名、file对象和SharedPreferencesImpl对象的对应关系private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;// 通过name获取sp对象public SharedPreferences getSharedPreferences(String name, int mode) {      File file;synchronized (ContextImpl.class) {// mSharedPrefsPaths缓存对象为null则创建if (mSharedPrefsPaths == null) {mSharedPrefsPaths = new ArrayMap<>();}// 从mSharedPrefsPaths缓存中获取file对象file = mSharedPrefsPaths.get(name);// 如果file对象为null,则创建file对象if (file == null) {// 通过路径获取file对象file = getSharedPreferencesPath(name);mSharedPrefsPaths.put(name, file);}}return getSharedPreferences(file, mode);}// 创建file对象public File getSharedPreferencesPath(String name) {return makeFilename(getPreferencesDir(), name + ".xml");}// 获取sp文件的根目录private File getPreferencesDir() {synchronized (mSync) {if (mPreferencesDir == null) {mPreferencesDir = new File(getDataDir(), "shared_prefs");}return ensurePrivateDirExists(mPreferencesDir);}}// 通过file对象获取sp对象public SharedPreferences getSharedPreferences(File file, int mode) {SharedPreferencesImpl sp;synchronized (ContextImpl.class) {// 从sSharedPrefsCache缓存获取ArrayMapfinal ArrayMap<File, SharedPreferencesImpl> cache = 							 													getSharedPreferencesCacheLocked();// 从缓存中获取sp对象sp = cache.get(file);if (sp == null) {// 如果sp对象为null,则创建并缓存checkMode(mode);     sp = new SharedPreferencesImpl(file, mode);cache.put(file, sp);return sp;}}return sp;}// 从sSharedPrefsCache缓存获取sp对象private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {// sSharedPrefsCache缓存对象为null则创建if (sSharedPrefsCache == null) {sSharedPrefsCache = new ArrayMap<>();}// 获取包名final String packageName = getPackageName();// sSharedPrefsCache通过包名获取ArrayMapArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);// packagePrefs为null则创建if (packagePrefs == null) {packagePrefs = new ArrayMap<>();sSharedPrefsCache.put(packageName, packagePrefs);}return packagePrefs;}
}

初始化和读取数据

// SharedPreferencesImpl.javafinal class SharedPreferencesImpl implements SharedPreferences {// 目标文件private final File mFile;// 锁private final Object mLock = new Object();// 文件是否加载private boolean mLoaded = false;SharedPreferencesImpl(File file, int mode) {mFile = file;mBackupFile = makeBackupFile(file);mMode = mode;mLoaded = false;mMap = null;mThrowable = null;// 开始加载文件startLoadFromDisk();}// 开启线程加载文件private void startLoadFromDisk() {synchronized (mLock) {mLoaded = false;}// 开启线程new Thread("SharedPreferencesImpl-load") {public void run() {loadFromDisk();}}.start();}// 加载文件private void loadFromDisk() {synchronized (mLock) {if (mLoaded) {return;}// 如果有备份文件,则恢复备份文件if (mBackupFile.exists()) {mFile.delete();mBackupFile.renameTo(mFile);}}Map<String, Object> map = null;  if (mFile.canRead()) {// 读取文件BufferedInputStream str = new BufferedInputStream(new FileInputStream(mFile), 16 * 1024);// 解析XML文件并转为Mapmap = (Map<String, Object>) XmlUtils.readMapXml(str);            }synchronized (mLock) {mLoaded = true;if (map != null) {// 使用新MapmMap = map;} else {mMap = new HashMap<>();}// 解析完文件后唤醒锁mLoaded = true;mLock.notifyAll();}}// 获取数据public String getString(String key, @Nullable String defValue) {synchronized (mLock) {// 查询数据时可能会阻塞等待awaitLoadedLocked();String v = (String)mMap.get(key);return v != null ? v : defValue;}}// 等待private void awaitLoadedLocked() {while (!mLoaded) {try {mLock.wait();} catch (InterruptedException unused) {}}}
}    

写入数据

虽然 ContextImpl 中使用了内存缓存,但是最终数据还是需要执行磁盘 IO 持久化到磁盘文件中。如果每一次 “变更操作” 都对应一次磁盘 “写回操作” 的话,不仅效率低下,而且没有必要。所以 SharedPreferences 会使用 “事务” 机制,将多次变更操作聚合为一个 “事务”,一次事务最多只会执行一次磁盘写回操作。
SharedPreferences 的事务操作由 Editor 接口实现。

// SharedPreferencesImpl.javafinal class SharedPreferencesImpl implements SharedPreferences {public Editor edit() {// 等待文件加载完成synchronized (mLock) {awaitLoadedLocked();}// 创建编辑器return new EditorImpl();}// 编辑器public final class EditorImpl implements Editor {// 锁对象private final Object mEditorLock = new Object();// 修改记录private final Map<String, Object> mModified = new HashMap<>();// 清除全部数据的标记private boolean mClear = false;// 存放String类型数据@Overridepublic Editor putString(String key, @Nullable String value) {synchronized (mEditorLock) {mModified.put(key, value);return this;}}// 存放int类型数据@Overridepublic Editor putInt(String key, int value) {synchronized (mEditorLock) {mModified.put(key, value);return this;}}// 删除数据@Overridepublic Editor remove(String key) {synchronized (mEditorLock) {mModified.put(key, this);return this;}}// 清除所有数据@Overridepublic Editor clear() {synchronized (mEditorLock) {mClear = true;return this;}}// 异步提交@Overridepublic void apply() {...}// 同步提交,并返回操作结果是否成功@Overridepublic boolean commit() {...}}   
}
MemoryCommitResult

MemoryCommitResult 是一个事务对象,收集需要写入磁盘的数据。

// MemoryCommitResult.javaprivate static class MemoryCommitResult {// 内存版本号final long memoryStateGeneration;// 写入磁盘的数据final Map<String, Object> mapToWriteToDisk;// 同步计数器  final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);@GuardedBy("mWritingToDiskLock")volatile boolean writeToDiskResult = false;boolean wasWritten = false;void setDiskWriteResult(boolean wasWritten, boolean result) {this.wasWritten = wasWritten;writeToDiskResult = result;// 唤醒等待锁writtenToDiskLatch.countDown();}
}
commitToMemory()
// SharedPreferencesImpl.javafinal class SharedPreferencesImpl implements SharedPreferences {// 每次提交数据自增 1,事务结束自减 1private int mDiskWritesInFlight = 0;// 内存版本private long mCurrentMemoryStateGeneration;// 磁盘版本private long mDiskStateGeneration;// 修改记录private final Map<String, Object> mModified = new HashMap<>();// 清除全部数据的标记private boolean mClear = false;// 全量提交到内存private MemoryCommitResult commitToMemory() {long memoryStateGeneration;boolean keysCleared = false;List<String> keysModified = null;Set<OnSharedPreferenceChangeListener> listeners = null;Map<String, Object> mapToWriteToDisk;synchronized (SharedPreferencesImpl.this.mLock) {// 表示有多个写入操作if (mDiskWritesInFlight > 0) {mMap = new HashMap<String, Object>(mMap);}// 需要写入磁盘的数据mapToWriteToDisk = mMap; // 全量Map// 自增加1mDiskWritesInFlight++;synchronized (mEditorLock) {// 是否发生有效修改boolean changesMade = false;// 清除全部数据if (mClear) {if (!mapToWriteToDisk.isEmpty()) {changesMade = true;mapToWriteToDisk.clear();}keysCleared = true;mClear = false;}// 将内存中的mModified都数据整合到mapToWriteToDisk中for (Map.Entry<String, Object> e : mModified.entrySet()) {String k = e.getKey();Object v = e.getValue();if (v == this || v == null) {// 如果value是this,表示删除这个keyif (!mapToWriteToDisk.containsKey(k)) {continue;}mapToWriteToDisk.remove(k);} else {// 修改key-value值if (mapToWriteToDisk.containsKey(k)) {Object existingValue = mapToWriteToDisk.get(k);if (existingValue != null && existingValue.equals(v)) {continue;}}mapToWriteToDisk.put(k, v);}// 标记修改完成changesMade = true;// 记录发生修改的keyif (hasListeners) {keysModified.add(k);}}// 重置内存中的mModifiedmModified.clear();// 修改完成,自增加1if (changesMade) {mCurrentMemoryStateGeneration++;}// 修改内存版本号memoryStateGeneration = mCurrentMemoryStateGeneration;}}return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,listeners, mapToWriteToDisk);}
}
commit()
// SharedPreferencesImpl.EditorImpl#commit()public boolean commit() {// 写入到内存,并获取事务对象MemoryCommitResult mcr = commitToMemory();// 写入到磁盘SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);// 写入等待mcr.writtenToDiskLatch.await();       // 触发回调监听notifyListeners(mcr);return mcr.writeToDiskResult;
}
apply()
// SharedPreferencesImpl.EditorImpl#apply()public void apply() {// 写入到内存,并获取事务对象final MemoryCommitResult mcr = commitToMemory();// 创建一个Runnablefinal Runnable awaitCommit = new Runnable() {@Overridepublic void run() {// 阻塞线程,直到磁盘操作执行完毕mcr.writtenToDiskLatch.await();}};// 将Runnable提交到QueuedWork中QueuedWork.addFinisher(awaitCommit);// 创建一个Runnable,写入成功后QueuedWork删除awaitCommit任务Runnable postWriteRunnable = new Runnable() {@Overridepublic void run() {awaitCommit.run();QueuedWork.removeFinisher(awaitCommit);}};// 提交写入磁盘任务SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);// 触发回调监听notifyListeners(mcr);
}
enqueueDiskWrite()
// SharedPreferencesImpl#enqueueDiskWrite()// postWriteRunnable为null表示commit同步提交,不为null表示apply异步提交
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {// 判断是否为同步操作final boolean isFromSyncCommit = (postWriteRunnable == null);// 写入磁盘任务final Runnable writeToDiskRunnable = new Runnable() {@Overridepublic void run() {synchronized (mWritingToDiskLock) {// 写入到磁盘writeToFile(mcr, isFromSyncCommit);}synchronized (mLock) {// 自减1mDiskWritesInFlight--;}if (postWriteRunnable != null) {postWriteRunnable.run();}}};// 如果是同步操作if (isFromSyncCommit) {// 判断mDiskWritesInFlight变量,如果存在并发写入则交给QueuedWorkboolean wasEmpty = false;synchronized (mLock) {wasEmpty = mDiskWritesInFlight == 1;}// wasEmpty为true,表示没有并发写入,则直接执行写入任务if (wasEmpty) {writeToDiskRunnable.run();return;}}// 如果是异步操作,提交到QueuedWork执行QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
writeToFile()
// SharedPreferencesImpl#writeToFile()private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {// 判断旧文件是否存在	boolean fileExists = mFile.exists();if (fileExists) {// 是否执行写入操作boolean needsWrite = false;// 如果磁盘版本小于内存版本,执行磁盘写入操作if (mDiskStateGeneration < mcr.memoryStateGeneration) {if (isFromSyncCommit) {// 如果是同步执行,一定执行写入操作needsWrite = true;} else {// 如果是异步执行,只有最新的内存版本菜执行写入操作synchronized (mLock) {if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {needsWrite = true;}}}}// 无效的异步写回,直接结束if (!needsWrite) {mcr.setDiskWriteResult(false, true);return;}// 文件备份boolean backupFileExists = mBackupFile.exists();if (!backupFileExists) {// 如果没有备份文件,将旧文件改为备份文件if (!mFile.renameTo(mBackupFile)) {mcr.setDiskWriteResult(false, false);return;}} else {// 如果有备份文件,则删除旧文件mFile.delete();}}try {// 执行写入操作,写入到xml文件中FileOutputStream str = createFileOutputStream(mFile);     XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);FileUtils.sync(str);str.close();ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);// 写入成功,直接删除备份文件mBackupFile.delete();// 同步磁盘版本号mDiskStateGeneration = mcr.memoryStateGeneration;// 写入成功mcr.setDiskWriteResult(true, true);return;} catch (XmlPullParserException e) {Log.w(TAG, "writeToFile: Got exception:", e);} catch (IOException e) {Log.w(TAG, "writeToFile: Got exception:", e);}// 写入失败会执行这里if (mFile.exists()) {if (!mFile.delete()) {Log.e(TAG, "Couldn't clean up partially-written file " + mFile);}}mcr.setDiskWriteResult(false, false);
}

主动等待写回任务结束

在 apply() 方法中,在执行 enqueueDiskWrite() 前创建了 awaitCommit 任务并加入到 QueudWork 等待队列,直到磁盘写回结束才将 awaitCommit 移除。这个 awaitCommit 任务是做什么的呢?

mcr.writtenToDiskLatch.await();

可以看到,在主线程的 Activity#onPause、Activity#onStop、Service#onStop、Service#onStartCommand 等生命周期状态变更时,会调用 QueudeWork.waitToFinish():

// ActivityThread.javapublic void handlePauseActivity(...) {performPauseActivity(r, finished, reason, pendingActions);if (r.isPreHoneycomb()) {QueuedWork.waitToFinish();}
}private void handleStopService(IBinder token) {QueuedWork.waitToFinish();ActivityManager.getService().serviceDoneExecuting(token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
}

waitToFinish() 会执行所有 sFinishers 等待队列中的 aWaitCommit 任务,主动等待所有磁盘写回任务结束。在写回任务结束之前,主线程会阻塞在等待锁上,这里也有可能发生 ANR。

总结

SharedPreferences 是一个轻量级的 K-V 存储框架。从源码分析中,可以看到 SharedPreferences 在读写性能、可用性方面都有做一些优化,例如:锁细化、事务化、事务过滤、文件备份等。

建议:

  • 因为SP初始化时会加载全部sp文件和全量提交,因此大文件尽可能的拆分多个文件,修改多个数据时尽可能一起提交。
  • 因为 commit() 是同步操作,apply() 是异步操作,因此推荐使用 apply()。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/246483.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Parallels Desktop 19 for Mac虚拟机 一键激活版

Parallels Desktop是一款功能强大的虚拟机软件&#xff0c;它允许用户在Mac电脑上同时运行Windows、Linux和其他操作系统。Parallels Desktop提供了直观易用的界面&#xff0c;使用户可以轻松创建、配置和管理虚拟机&#xff0c;该软件具有快速启动和关闭虚拟机的能力&#xff…

Servlet 与 MVC

主要内容 Servlet 重点 MVC 重点 Filter 重点 章节目标 掌握 Servlet 的作用 掌握 Servlet 的生命周期 掌握 JSP 的本质 掌握 MVC 的设计思想 掌握 Filter 的作用及使用场景 第一节 Servlet 1. Servlet 概念 Servlet 是在服务器上运行的能够对客户端请求进行处理&a…

从零开发短视频电商 Tesseract OCR识别增强

文章目录 概要图像预处理阶段默认反转图像重新缩放二值化噪音消除膨胀/腐蚀旋转/偏移校正边框缺少边框边框太大扫描边框去除 透明度/Alpha通道 引擎处理阶段语言模型配置提高识别速度词典、单词列表和模式表格识别 使用 Tesseract OCR 的 GUI 和其他项目 原文如下&#xff1a; …

【网络协议测试】畸形数据包——圣诞树攻击(DOS攻击)

简介 TCP所有标志位被设置为1的数据包被称为圣诞树数据包&#xff08;XMas Tree packet&#xff09;&#xff0c;之所以叫这个名是因为这些标志位就像圣诞树上灯一样全部被点亮。 标志位介绍 TCP报文格式&#xff1a; 控制标志&#xff08;Control Bits&#xff09;共6个bi…

安全基础~通用漏洞1

文章目录 知识补充Acess数据库注入MySQL数据库PostgreSQL-高权限读写注入MSSQL-sa高权限读写执行注入Oracle 注入Mongodb 注入sqlmap基础命令 知识补充 order by的意义&#xff1a; union 操作符用于合并两个或多个 select语句的结果集。 union 内部的每个 select 语句必须拥有…

MVC架构模式与三层架构

提示&#xff1a;博客中的图片来源于动力节点在B站的视频讲解。 MVC架构模式与三层架构 一、三层架构二、MVC&#xff08;model view controller&#xff09;1.MVC 架构的工作流程&#xff08;1&#xff09;JSP Servlet javabean实现MVC。&#xff08;2&#xff09;SSM&#…

防御保护---安全策略

文章目录 一.安全策略概述 概述&#xff1a; 安全策略的作用&#xff1a; 包过滤防火墙的安全风险 状态检测防火墙访问过程 安全策略与传统防火墙的区别 二.案例分析 基础配置&#xff1a;&#xff08;正常数通&#xff09; 安全策略配置 练习 一.安全策略概述 概述&#xff1…

数据挖掘笔记1

课程&#xff1a;清华大学-数据挖掘&#xff1a;理论与算法&#xff08;国家级精品课&#xff09;_哔哩哔哩_bilibili 一、Learning Resources 二、Data 数据是最底层的一种表现形式。数据具有连续性。从存储上来讲&#xff0c;数据分为逻辑上的和物理层的。大数据&#xff1…

Elasticsearch8.11集群部署

集群就是多个node统一对外提供服务&#xff0c;避免单机故障带来的服务中断&#xff0c;保证了服务的高可用&#xff0c;也因为多台节点协同运作&#xff0c;提高了集群服务的计算能力和吞吐量。ES是一个去中心化的集群&#xff0c;操作一个节点和操作一个集群是一样的&#xf…

Jmeter接口测试总结

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Jmeter介绍&测试准备 Jmeter介绍&#xff1a;Jmeter是软件…

LeetCode:1706. 球会落何处(Java 模拟)

目录 1706. 球会落何处 题目描述&#xff1a; 实现代码与解析&#xff1a; 原理思路&#xff1a; 1706. 球会落何处 题目描述&#xff1a; 用一个大小为 m x n 的二维网格 grid 表示一个箱子。你有 n 颗球。箱子的顶部和底部都是开着的。 箱子中的每个单元格都有一个对角线…

ubuntu 20.04 使用 webrtc-streamer自动退出,报错GLIBC 问题解决方法

文章目录 前言Ubuntu 20.4中使用webrtc-streamer报错总结 前言 前端vue2 项目需要播放海康的视频流&#xff0c;本地启动起来了&#xff0c;现在需要的服务器上部署&#xff0c;服务器是Ubuntu 20.04&#xff0c;下面是部署时遇到的问题及解决方法&#xff0c;总耗时2天。 不知…

Chain-of-Thought Prompting Elicits Reasoning in Large Language Models导读

通过生成一系列中间推理步骤&#xff08;即“思维链”&#xff09;显著提高大型语言模型进行复杂推理的能力 这篇论文探讨了如何通过生成一系列中间推理步骤&#xff08;即“思维链”&#xff09;显著提高大型语言模型进行复杂推理的能力。研究人员使用一种简单的方法——思维…

司铭宇老师:汽车销售培训:汽车销售员培训:汽车销售技巧培训:汽车销售技巧和话术

汽车销售培训&#xff1a;汽车销售员培训&#xff1a;汽车销售技巧培训&#xff1a;汽车销售技巧和话术 汽车销售是一项充满挑战性的工作&#xff0c;它需要销售人员具备良好的沟通技巧、谈判技巧以及产品讲解能力。在这篇文章中&#xff0c;我们将详细探讨汽车销售中的技巧和话…

iOS推送通知

文章目录 一、推送通知的介绍1. 简介2. 通知的分类 二、本地通知1. 本地通知的介绍2. 实现本地通知3. 监听本地通知的点击 三、远程通知1. 什么是远程通知2. 为什么需要远程通知3. 远程通知的原理4. 如何做远程通知5. 远程通知证书配置6. 获取远程推送要用的 DeviceToken7. 测试…

Spring Security 存储密码之 JDBC

Spring Security的JdbcDaoImpl实现了UserDetailsService接口,通过使用JDBC提供支持基于用户名和密码的身份验证。 JdbcUserDetailsManager扩展了JdbcDaoImpl,通过UserDetailsManager接口提供UserDetails的管理功能。 当Spring Security配置为接受用户名/密码进行身份验证时,…

5|领域建模实践(上):怎样既准确又深刻地理解业务知识?

上节课咱们完成了事件风暴&#xff0c;梳理了系统的行为需求。但你可能也发现了&#xff0c;其实还有些微妙的业务概念还没有澄清&#xff0c;这就要靠领域建模来完成了。 建立领域模型是 DDD 的核心。要建好领域建模&#xff0c;需要理论和实践相结合。由于我们的模型有一定的…

vue3+elementPlus pc和小程序ai聊天文生图

websocket封装可以看上一篇文章 //pc端 <template><div class"common-layout theme-white"><el-container><el-aside><div class"title-box"><span>AI Chat</span></div><div class"chat-list&…

【软件测试】学习笔记-构建并执行 JMeter 脚本的正确姿势

有些团队在组建之初往往并没有配置性能测试人员&#xff0c;后来随着公司业务体量的上升&#xff0c;开始有了性能测试的需求&#xff0c;很多公司为了节约成本会在业务测试团队里选一些技术能力不错的同学进行性能测试&#xff0c;但这些同学也是摸着石头过河。他们会去网上寻…

一天吃透计算机网络面试八股文

面试网站&#xff1a;topjavaer.cn 目录&#xff1a; 网络分层结构三次握手两次握手可以吗&#xff1f;四次挥手第四次挥手为什么要等待2MSL&#xff1f;为什么是四次挥手&#xff1f;TCP有哪些特点&#xff1f;说说TCP报文首部有哪些字段&#xff0c;其作用又分别是什么&…