复习了下SharePreference的使用,以及了解下SharePreference的源码实现,解决多进程情况下的SharePreference问题,做下笔记。

参考文章:

源码分析:

www.jianshu.com/p/8eb2147c3…

www.jianshu.com/p/3b2ac6201…

SharePreference的多进程解决方案:

juejin.im/entry/59083…

SharePreference

Android平台中一个轻量级的存储库,用来保存应用程序的各种配置信息。本质是一个以“key-value”键值对的方式保存数据的xml文件。

文件保存地址:在/data/data/package name/shared_prefs目录下就可以查看到这个文件了

简单使用例子

        //获取得到SharePreference,第一个参数是文件名称,第二个参数是操作模式//一般是MODE_PRIVATE模式,指定该SharedPreferences数据只能被本应用程序读、写SharedPreferences sharedPreferences = getSharedPreferences("test", MODE_PRIVATE);//创建Editor对象SharedPreferences.Editor editor=sharedPreferences.edit();//保存数据editor.putString("name","donggua");//editor.commit();editor.apply();//读取数据String result=sharedPreferences.getString("name","默认值");
复制代码

commit和apply的区别

当使用commit去提交数据的时候,发现IDE提示让我们使用apply方法。

  • commit:同步提交,commit将同步的把数据写入磁盘和内存缓存,并且有返回值。
  • apply:异步提交,会把数据同步写入内存缓存,然后异步保存到磁盘,可能会失败,失败不会收到错误回调。

两者的区别:

  • commit的效率会比apply慢一点。在一个进程中,如果在不关心提交结果是否成功的情况下,优先考虑apply方法。
  • 都是原子性操作,但是原子的操作不同。commit的从数据提交到保存到内存后再保存到磁盘中,中间不可打断。而apply方法是将数据保存到内存后就可以返回了,异步执行保存到磁盘的操作,

源码分析

获取SharePreference对象

利用Context获取到SharePreference实例,ContextImpl是Context的实现类,实现了getSharedPreferences方法。

  • 因为SharedPreferences是支持自定义文件名的,所以这里利用了ArrayMap<File, SharedPreferencesImpl>来缓存不同文件对应的SharedPreferencesImpl对象。一个File文件对应一个SharePreference对象。
  • getSharedPreferencesCacheLocked(),获取缓存的ArrayMap<File, SharedPreferencesImpl>对象,没有则创建一个。
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {SharedPreferencesImpl sp;synchronized (ContextImpl.class) {//获取缓存的mapfinal ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();//拿到对应的文件的SharePreferencesp = cache.get(file);if (sp == null) {checkMode(mode);if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {if (isCredentialProtectedStorage()&& !getSystemService(UserManager.class).isUserUnlockingOrUnlocked(UserHandle.myUserId())) {throw new IllegalStateException("SharedPreferences in credential encrypted "+ "storage are not available until after user is unlocked");}}//没有缓存,创建SharedPreferencesImpl对象,并保存到缓存中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;
}@GuardedBy("ContextImpl.class")
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {if (sSharedPrefsCache == null) {sSharedPrefsCache = new ArrayMap<>();}final String packageName = getPackageName();ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);if (packagePrefs == null) {packagePrefs = new ArrayMap<>();sSharedPrefsCache.put(packageName, packagePrefs);}return packagePrefs;
}
复制代码

SharedPreferencesImpl是SharedPreferences接口的实现类,实现了commit和apply方法。先看看SharedPreferencesImpl的构造方法。会异步调用一个startLoadFromDisk的方法,作用是从磁盘中把SharePreference文件里面保存的xml信息读取到内存中,并保存到Map里面。

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(){
//省略部分代码
try {stat = Os.stat(mFile.getPath());if (mFile.canRead()) {BufferedInputStream str = null;try {str = new BufferedInputStream(new FileInputStream(mFile), 16 * 1024);//进行xml解析map = (Map<String, Object>) XmlUtils.readMapXml(str);} catch (Exception e) {Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);} finally {IoUtils.closeQuietly(str);}}
} catch (ErrnoException e) {// An errno exception means the stat failed. Treat as empty/non-existing by// ignoring.
} catch (Throwable t) {thrown = t;
}
}//省略部分代码。//将解析结果保存的map进行赋值
if (map != null) {mMap = map;mStatTimestamp = stat.st_mtim;mStatSize = stat.st_size;
}
复制代码

读取数据

例如SharedPreferencesImpl的实现getString()方法,是直接从内存中的mMap直接就把数据读取出来,并没有涉及到磁盘操作。(恍然大悟,以前以为读取数据也要去读取file文件)

@Override
@Nullable
public String getString(String key, @Nullable String defValue) {synchronized (mLock) {awaitLoadedLocked();String v = (String)mMap.get(key);return v != null ? v : defValue;}
}
复制代码

保存数据

EditorImpl类实现了Editor接口。apply和commit都会调用commitToMemory方法,将数据保存到内存中,后面调用enqueueDiskWrite将数据保存到磁盘中。

//临时缓存多个key的数据,后面提交数据的时候,就遍历这个map就行
private final Map<String, Object> mModified = new HashMap<>();
//CountDownLatch,等待直到保存到磁盘的操作完成。
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);//在当前线程直接写文件,调用await,同步等待,最后返回操作的结果result
@Override
public boolean commit() {long startTime = 0;if (DEBUG) {startTime = System.currentTimeMillis();}//保存到内存中MemoryCommitResult mcr = commitToMemory();//保存到磁盘SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null /* sync write on this thread okay */);try {mcr.writtenToDiskLatch.await();} catch (InterruptedException e) {return false;} finally {if (DEBUG) {Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration+ " committed after " + (System.currentTimeMillis() - startTime)+ " ms");}}notifyListeners(mcr);return mcr.writeToDiskResult;
}//异步等待保存操作,无法获取操作的结果
@Override
public void apply() {final long startTime = System.currentTimeMillis();//保存到内存中final MemoryCommitResult mcr = commitToMemory();final Runnable awaitCommit = new Runnable() {@Overridepublic void run() {try {mcr.writtenToDiskLatch.await();} catch (InterruptedException ignored) {}if (DEBUG && mcr.wasWritten) {Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration+ " applied after " + (System.currentTimeMillis() - startTime)+ " ms");}}};QueuedWork.addFinisher(awaitCommit);Runnable postWriteRunnable = new Runnable() {@Overridepublic void run() {awaitCommit.run();QueuedWork.removeFinisher(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);
}
复制代码

多进程中的SharePreference

上面讲的默认是单进程中的SharePreference,读取操作是直接从内存中的Map读取的,不涉及IO操作。如果是在多进程中的话,不同进程之间的内存并不是共享的,这个时候读写同一个SharePreference就会出现问题了。比如多个进程对同一个sharedpreference进行修改,总会有一个进程获取到的结果不是实时修改后的结果。

解决方法:推荐使用ContentProvider来处理多进程间的文件共享。

ContentProvider的特点:

  • ContentProvider内部的同步机制会防止多个进程同时访问,避免数据冲突。
  • ContentProvider的数据源,并不是只能选择数据库,其实核心操作就在update()和query()这两个操作,里面操作存取的数据源其实可以根据我们需要,替换成文件,也可以换成SharedPreferences。

所以我们可以使用ContentProvider做了一下中间媒介,让它帮我们实现多进程同步机制,里面操作的数据改成SharedPreferences来实现。这样的话就可以实现了跨进程访问SharePreference。

下面简单地写一个demo,读取的时候只需要传进相应的uri就行了。比如下面的代码,path字段的第二个是fileName,第三个是key值。

public class MultiProcessSharePreference extends ContentProvider{
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {//获取xml的文件名,默认取path字段的第一个Log.d(TAG, "query: uri:" + uri);String tableName = uri.getPathSegments().get(0);String name = uri.getPathSegments().get(1);String key = uri.getPathSegments().get(2);Log.d(TAG, "query: tableName:" + tableName);Log.d(TAG, "query: fileName:" + name);Log.d(TAG, "query: key:" + key);//创建sharedPreferences对象SharedPreferences sharedPreferences = getContext().getSharedPreferences(name, Context.MODE_PRIVATE);//创建一个cursor对象MatrixCursor cursor = null;switch (uriMatcher.match(uri)) {case CODE_PREFERENCE_STRING:String value = sharedPreferences.getString(key, "默认值");cursor = new MatrixCursor(PREFERENCE_COLUMNS, 1);MatrixCursor.RowBuilder rowBuilder = cursor.newRow();rowBuilder.add(value);break;default:Log.d(TAG, "query: Uri No Match");}return cursor;
}
}
复制代码
  • MatrixCursor: 如果需要一个cursor而没有一个现成的cursor的话,那么可以使用MatrixCursor实现一个虚拟的表。MatrixCursor.RowBuilder是用来添加Row数据的,通过rowBuilder的add方法,就可以把数值添加到行里面了。使用场景:比如ContentProvider的query方法是返回一个cursor类型的数据,而数据源用的是SharePreference,这个时候就可以利用MatrixCursor。MartixCursor本质上是用一个一位数据来模拟一个二维数据,根据行值和列值就可以找到对应的数据了。

MatrixCursor的源码解析:blog.csdn.net/zhang_jun_l…

//定义每一列的字段名字
public static final String COLUMN_VALUE = "value";
//创建一个字符数组,字符数组的值对应着表的字段
private static String[] PREFERENCE_COLUMNS = {COLUMN_VALUE};
//构造一个MatrixCursor对象
MatrixCursor  cursor = new MatrixCursor(PREFERENCE_COLUMNS, 1);
//通过matrixCursor的addRow方法添加一行值
MatrixCursor.RowBuilder rowBuilder = cursor.newRow();
rowBuilder.add(value);
复制代码
  • 优化一下的思路:

    • 在ContentProvider里面加一个HashMap<String,SharePreference>进行一下缓存,key值是文件名,value是对应的SharePreference对象,这样的话,就不用每次都去加载SharePreference对象了。
    • 在ContentProvider里面实现回调listener,在key值有变化的时候,进行通知订阅者。

SharePreference源码学习和多进程的场景相关推荐

  1. 第十四课 k8s源码学习和二次开发原理篇-调度器原理

    第十四课 k8s源码学习和二次开发原理篇-调度器原理 tags: k8s 源码学习 categories: 源码学习 二次开发 文章目录 第十四课 k8s源码学习和二次开发原理篇-调度器原理 第一节 ...

  2. clickhouse-jdbc 源码学习

    clickhouse-jdbc 源码学习 文章目录 clickhouse-jdbc 源码学习 包介绍 依赖版本 搭建环境版本如下 QA 1.LocalDate/LocalDateTime不兼容 2.一 ...

  3. 第八课 k8s源码学习和二次开发原理篇-KubeBuilder使用和Controller-runtime原理

    第八课 k8s源码学习和二次开发原理篇-KubeBuilder使用和Controller-runtime原理 tags: k8s 源码学习 categories: 源码学习 二次开发 文章目录 第八课 ...

  4. Electron源码学习: Electron组成与初始化流程

    Electron源码学习: Electron组成与结构 前言 ​ 最近研究学习Electron的源码结构已经有一些小的进展, 越接触Electron就越发现组成这个软件的大集合不得了.现在学习到的仍然 ...

  5. Vue源码学习 - 组件化(三) 合并配置

    Vue源码学习 - 组件化(三) 合并配置 合并配置 外部调用场景 组件场景 总结 学习内容和文章内容来自 黄轶老师 黄轶老师的慕课网视频教程地址:<Vue.js2.0 源码揭秘>. 黄轶 ...

  6. 源码学习-net/http

    package net/http是Go语言的主要应用场景之一web应用的基础,从中可以学习到大量前文提到的io,以及没有提到的sync包等一系列基础包的知识,代码量也相对较多,是一个源码学习的宝库.本 ...

  7. fasttext源码学习(2)--模型压缩

    fasttext源码学习(2)–模型压缩 前言 fasttext模型压缩的很明显,精度却降低不多,其网站上提供的语种识别模型,压缩前后的对比就是例证,压缩前126M,压缩后917K.太震惊了,必须学习 ...

  8. Vuex 4源码学习笔记 - Vuex是怎么与Vue结合?(三)

    在上一篇笔记中:Vuex源码学习笔记 - Vuex开发运行流程(二) 我们通过运行npm run dev命令来启动webpack,来开发Vuex,并在Vuex的createStore函数中添加了第一个 ...

  9. Java并发包源码学习系列:同步组件CountDownLatch源码解析

    文章目录 CountDownLatch概述 使用案例与基本思路 类图与基本结构 void await() boolean await(long timeout, TimeUnit unit) void ...

最新文章

  1. 同一订单类型用多个号码段
  2. [C#泛型系列文章]
  3. 常用SQL Server 小语法、函数 等的实例汇总
  4. PyTorch报错No module named ‘transforms‘
  5. Nacos客户端配置
  6. ctf-HITCON-2016-houseoforange学习
  7. F5 V9 利用SCCP实现带外管理及重装系统
  8. 苹果终端date命令_苹果M1 Mac电脑关闭SIP方法
  9. Windows便签快捷键
  10. android 开门动画,仿微信 首次启动 使用教程滑动 开门动画
  11. python取字符串首字母_python字符串操作
  12. oracle 建表 lob cache,创建表规范 lob 字段
  13. led显示屏播控服务器,显示屏集中播控方案--NovaPluto.pdf
  14. 小游戏------扫雷
  15. 使用repotrack下载指定rpm包及其全量依赖包
  16. 软件测试2022年终总结
  17. dk 识别物体出现的问题
  18. 用Socket实现点对点的文件传输
  19. 快速修复IE浏览器方案
  20. java女程序员工作_女生适合做java程序员吗 女java程序员好找工作

热门文章

  1. php写一个轻量级的容器,PHP写的一个轻量级的DI容器类
  2. wps不能打印_Excel表格怎么打印在一张纸上?
  3. HTTP代理神器Fidder
  4. 安装yum软件包的方法
  5. 数据结构——基本概念
  6. python运行不了、显示警告_Python中偶尔遇到的细节疑问(二):UnicodeDecodeError,警告与忽略警告warnings...
  7. Axure中推动拉动元件不生效_单线图标元件库SVG矢量图标 纯净无干扰的Axure元件库...
  8. php中mb substr,php中中文截取函数mb_substr()详细
  9. 数据结构和算法:第八章 图论算法
  10. mysql istransient_由于 MySQL 版本问题导入报错