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.mzph.cn/news/650229.shtml

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

相关文章

2024初学编曲免费软件FL Studio21.2.2

FL Studio在业内也被称作“水果”软件&#xff0c;这是一款功能强大、简单易上手的专业编曲软件。软件中的音效插件库拥有超过25种音效插件&#xff0c;能够帮助激发我们的创作灵感。而FL Studio中文还推出了训练营课程&#xff0c;初学者可以在训练营中进行编曲知识的学习&…

Android消息推送 SSE(Server-Sent Events)方案实践

转载请注明出处&#xff1a;https://blog.csdn.net/kong_gu_you_lan/article/details/135777170 本文出自 容华谢后的博客 0.写在前面 最近公司项目用到了消息推送功能&#xff0c;在技术选型的时候想要找一个轻量级的方案&#xff0c;偶然看到一篇文章讲ChatGPT的对话机制是基…

探索半导体制造业中的健永科技RFID读写器的应用方案

一、引言 在当今高度自动化的工业环境中&#xff0c;无线射频识别&#xff08;RFID&#xff09;技术已经成为实现高效生产的重要一环。特别是在半导体制造业中&#xff0c;由于产品的高价值和复杂性&#xff0c;生产过程的追踪和管理显得尤为重要。健永科技RFID读写器以其出色…

Java程序设计实验7 | IO流

*本文是博主对Java各种实验的再整理与详解&#xff0c;除了代码部分和解析部分&#xff0c;一些题目还增加了拓展部分&#xff08;⭐&#xff09;。拓展部分不是实验报告中原有的内容&#xff0c;而是博主本人自己的补充&#xff0c;以方便大家额外学习、参考。 目录 一、实验…

【办公技巧】Excel只读模式怎么改成编辑模式?

Excel文件打开之后&#xff0c;大家可能经常会遇到文件处于只读模式的情况&#xff0c;那么我们应该如何将excel只读模式改成编辑模式呢&#xff1f;今天和大家分享几种情况的解决方法。 首先&#xff0c;大部分的只读模式&#xff0c;打开之后都是没有密码的&#xff0c;只是…

LabVIEW振动信号分析

LabVIEW振动信号分析 介绍如何使用LabVIEW软件实现希尔伯特-黄变换&#xff08;Hilbert-Huang Transform, HHT&#xff09;&#xff0c;并将其应用于振动信号分析。HHT是一种用于分析非线性、非平稳信号的强大工具&#xff0c;特别适用于旋转机械等复杂系统的振动分析。开发了…

一款强大的矢量图形设计软件:Adobe Illustrator 2023 (AI2023)软件介绍

Adobe Illustrator 2023 (AI2023) 是一款强大的矢量图形设计软件&#xff0c;为设计师提供了无限创意和畅行无阻的设计体验。AI2023具备丰富的功能和工具&#xff0c;让用户可以轻松创建精美的矢量图形、插图、徽标和其他设计作品。 AI2023在界面和用户体验方面进行了全面升级…

单片机学习笔记---矩阵键盘

目录 矩阵键盘的介绍 独立按键和矩阵按键的相同之处&#xff1a; 矩阵按键的扫描 代码演示 代码模块化移植 Keil自定义模板步骤&#xff1a; 代码编写 矩阵键盘就是开发板上右下角的这个模块 这一节的代码是基于上一节讲的LCD1602液晶显示屏驱动代码进行的 矩阵键盘的介…

新版UI界面影视小程序亲测无问题带详细搭建教程

新版UI界面影视小程序亲测无问题带详细搭建教程 环境php7.0 — fileinfo–redis–sg11 mysql5.5 apache2.4 添加站点php7.0—-创建ftp—-上传后端文件《后端文件修改&#xff0c;/maccms/wxapi/config/dbs.php–修改当前数据库》—-设置ssl—-打开数据库安装cms 安装好后管…

仰暮计划|“星星之火可以燎原,平凡人的一生同样值得称赞

传递助老之情&#xff0c;践行为老初心。为学习和发扬助老为老精神&#xff0c;我参与了康乐忆享实践队开展的以“仰暮计划”为主题的实践活动&#xff0c;在实践过程中了解老人的人生经历&#xff0c;传播尊老爱老思想。我与老人谭爷爷在谈论家常时&#xff0c;他拿出年轻时的…

【项目日记(五)】第二层: 中心缓存的具体实现(上)

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:项目日记-高并发内存池⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你做项目   &#x1f51d;&#x1f51d; 开发环境: Visual Studio 2022 项目日…

CentOS安装Redis教程-shell脚本一键安装配置

文章目录 前言一、Redis单机版安装教程1. 复制脚本2. 增加执行权限3. 执行脚本 二、Redis扩展集群版安装教程1. 安装Redis单机版2. 复制脚本3. 增加执行权限4. 执行脚本5. 测试6. redis_cluster.sh 命令6.1 启动Redis扩展集群6.2 停止Redis扩展集群6.3 查看Redis扩展集群节点信…

AI编译器的前端优化策略

背景 工作领域是AI芯片工具链相关&#xff0c;很多相关知识的概念都是跟着项目成长建立起来&#xff0c;但是比较整个技术体系在脑海中都不太系统&#xff0c;比如项目参与中涉及到了很多AI编译器开发相关内容&#xff0c;东西比较零碎&#xff0c;工作中也没有太多时间去做复盘…

Docker容器(自定义镜像,Dockerfile,网桥,DockerCompose)

自定义镜像 镜像就是包含了应用程序、程序运行的系统函数库、运行配置等文件的文件包。构建镜像的过程其实就是把上述文件打包的过程。 构建步骤 镜像结构 Dockerfile 它是一个文本文件&#xff0c;包含很多指令&#xff0c;用指令来说明要执行什么操作来构建镜像。 官网&am…

【漏洞复现】友讯D-Link路由器弱口令漏洞

Nx01 产品简介 友讯电子设备&#xff08;上海&#xff09;有限公司于2002年8月13日成立。公司经营范围包括区内以路由器、网络卡、集线器、交换器、转换器等。 Nx02 漏洞描述 友讯D-Link路由器存在默认口令(admin/admin)&#xff0c;攻击者可利用该漏洞获取敏感信息。 Nx03 产…

windows .vscode的json文件配置 CMake 构建项目 调试窗口中文设置等

一、CMake 和 mingw64的安装和环境配置 二、tasks.json和launch.json文件配置 tasks.json {"version": "2.0.0","options": {"cwd": "${workspaceFolder}/build"},"tasks": [{"type": "shell&q…

软件设计师——计算机网络(四)

&#x1f4d1;前言 本文主要是【计算机网络】——软件设计师——计算机网络的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1…

react中优化类名写法(类似与vue的动态class对象方式)

安装和引入方式 npm install classnamesimport classNames form classsnames//render 方法中&#xff0c;需要动态className的地方直接参照上图使用

【NodeJS JS】动态加载字体的各方式及注意事项;

首先加载字体这个需求基本只存在于非系统字体&#xff0c;系统已有字体不需要加载即可直接使用&#xff1b; 方案1&#xff1a;创建 style 标签&#xff0c;写入 font-face{font-family: xxx;src: url(xxx)} 等相关字体样式&#xff1b;将style标签添加到body里&#xff1b;方…

C++学习| QT下载安装、VS配置QT

QT介绍 Qt&#xff1a;1991年由Qt Company开发的跨平台C图形用户界面应用程序开发框架。 应用&#xff1a;既可以开发GUI程序&#xff0c;也可用于开发非GUI程序&#xff0c;比如控制台工具和服务器。 对比MFC&#xff1a;MFC和QT两者都是用于C图形用户界面应用程序。 跨平…