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.hqwc.cn/news/434705.html

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

相关文章

Modelarts还能做预测银行存款,我的自动学习案例上新了

前言 最近我计划学习一下机器学习的相关技术&#xff0c;之前体验华为云CodeArts Snap的时候&#xff0c;重拾了一下Python。 然后就信心满满的打开了Python机器学习的教程&#xff0c;发现比想象中的难。 总觉得欠缺了些什么支撑自己的学习兴趣&#xff0c;正好最近在体验M…

前景贴纸类特效SDK,面向企业的技术解决方案

随着数字媒体技术的快速发展&#xff0c;视频内容在社交媒体、广告、教育等领域的应用越来越广泛。为了增加视频的吸引力和趣味性&#xff0c;许多企业开始寻求在视频中添加特效和贴纸。美摄科技的前景贴纸类特效SDK为企业提供了一种高效、灵活的解决方案&#xff0c;满足不同的…

循环的乐章与爱情的旋律

循环的乐章与爱情的旋律 The Rhapsody of Loops and the Melody of Love 在一个阳光明媚的Java编程课上&#xff0c;男主角林浩然&#xff0c;一个热衷于代码逻辑和算法谜题的大二学生&#xff0c;正沉浸在他的Java世界里。而女主角杨凌芸&#xff0c;则是班级中出了名的“程序…

【C++干货铺】C++中的IO流和文件操作

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 C语言的输入输出 流是什么&#xff1f; C的IO流 C标准IO流 C文件IO流 文本文件读写 二进制文件的读写 stringstream的简单介绍 将数值类型数据格式化为字…

【排序4】探秘归并排序:提高程序效率的必备技巧

&#x1f60a;归并排序 &#x1f38a;1、基本思想&#x1f38a;2、代码示例&#x1f38a;3、非递归实现&#x1f38a;4、归并排序的性能分析&#x1f38a;5、归并排序的优缺点&#x1f38a;6、归并排序的应用场景&#x1f38a;7、总结 &#x1f38a;1、基本思想 归并排序&…

Android开发学习-Activity

启停活动页面 1、启动和停止 startActivity(new Intent(原页面.this,目标页面.this)); startActivity(new Intent(this,ActFinishActivity.class)) 从当前页面回到上一个页面&#xff0c;相当于关闭当前页面&#xff0c;返回代码如下&#xff1a; finish(); 2、生命周期 …

【王道数据结构】【chapter线性表】【P44t16】

设有一个长度为n&#xff08;n为偶数&#xff09;的不带头结点的单链表且结点值都大于0&#xff0c;设计算法求这个单链表的最大的孪生和。孪生和的定义为一个结点值与其孪生结点值的和&#xff0c;对于第i个结点&#xff08;从0开始&#xff09;&#xff0c;其孪生结点为第n-i…

十分钟搭建本地Linux开发运行环境

十分钟搭建本地开运行环境 linux环境请参考&#xff1a;5分钟搭建本地linux开发环境 环境&#xff1a;宝塔、Jdk、Mysql、Redis 1、宝塔&#xff1a; 官网地址&#xff1a;宝塔官网 yum install -y wget && wget -O install.sh https://download.bt.cn/install/in…

安科瑞宿舍安全用电监测:科技保障,安全无忧

在当今社会&#xff0c;电力已成为我们日常生活中不可或缺的一部分。然而&#xff0c;不正确的用电方式或管理不善可能会引发火灾等安全事故&#xff0c;给学生带来生命财产威胁。为了解决这一问题&#xff0c;安科瑞宿舍安全用电监测系统应运而生&#xff0c;为学生的用电安全…

力扣1312. 让字符串成为回文串的最少插入次数

动态规划 思路&#xff1a; 通过插入字符构造回文串&#xff0c;要想插入次数最少&#xff0c;可以将字符串 s 的逆序 s 进行比较找出最长公共子序列&#xff1b;可以先分析&#xff0c;字符串 s 通过插入得到回文串 ps&#xff0c;其中间的字符应该不会变化&#xff1a; 若 s…

仅使用 Python 创建的 Web 应用程序(前端版本)第09章_购物车

在本章中,我们将实现购物车页面。 完成后的图像如下。 创建过程与之前相同,如下。 No分类内容1Model创建继承BaseDataModel的数据类Cart、CartItem2Service创建一个 CartAPIClient3Page定义PageId并创建继承自BasePage的页面类4Application将页面 ID 和页面类对添加到 Multi…

ZYNQ AC7020C的“点LED”实验

一、创建 Vivado 工程 1、启动 Vivado 2、在 Vivado 开发环境里点击“Create New Project”&#xff0c;创建一个新的工程 3、弹出一个建立新工程的向导&#xff0c;点击“Next” 4、在弹出的对话框中输入工程名和工程存放的目录。需要注意工程路径“Project location”不能有…