史上最全面,清晰的SharedPreferences解析

基础用法
获取Sp:
get
put
监听器
原理分析
获取SharedPreferences
构造SharedPreferences
getX原理分析
putX原理分析
创建editor
putString
apply
apply总结
commit
SharedPreferences最佳实践
勿存储过大value
勿存储复杂数据
不要乱edit和apply,尽量批量修改一次提交
建议apply,少用commit
registerOnSharedPreferenceChangeListener弱引用问题
apply和commit对registerOnSharedPreferenceChangeListener的影响
不要有任何用SP进行多进程存储的幻想
基础用法

获取Sp:

Activity中:getPreferences(int mode)
context.getSharedPreferences(String name, int mode)
PreferenceManager.getDefaultSharedPreferences(Context context)
1和3的获取SP的方法最终都会调用2,只是1和3默认选取了特定的name,1中通过getLocalClassName()获取通过包名和类名拼装的name,3通过context.getPackageName() + "_preferences"获取name
注意第二个参数的含义,现在均指定为MODE_PRIVATE,其余的都被废弃。含义如下:File creation mode: the default mode, where the created file can only be accessed by the calling application (or all applications sharing the same user ID).
所存储的数据保存在:/data/data/<package name>/shared_prefs下的指定name.xml文件中
get

sp.getX(String key, X value);
1
X为简单的基本类型:float,int,long,String,Boolean,Set
put

SharedPreferences sharedPreferences = getPreferences(0);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putFloat("float", 1f);
editor.putBoolean("boolean", true);
editor.apply();
1
2
3
4
5
首先获取Editor对象,操作完需要进行事务提交操作,可以采用commit或者apply进行。commit同步写磁盘,返回是否成功的标识码。apply异步写磁盘,无返回值。(二者均是同步写内存,先同步写内存,之后同步/异步写磁盘)
监听器

sharedPreferences.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

}
});
1
2
3
4
5
6
如果使用匿名内部类的形式进行监听。注意,因为OnSharedPreferenceChangeListener的引用被保存在一个WeakHashMap中,导致程序的行为不确定性。为了避免这种情况,推荐以下方式:
private OnSharedPreferenceChangeListener mListener = new OnSharedPreferenceChangeListener() {

@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
Log.i(LOGTAG, "instance variable key=" + key);
}
};

@Override
protected void onResume() {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).registerOnSharedPreferenceChangeListener(mListener);
super.onResume();
}

@Override
protected void onPause() {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).unregisterOnSharedPreferenceChangeListener(mListener);
super.onPause();
原理分析

获取SharedPreferences

public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
if (sSharedPrefs == null) {
sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
}

final String packageName = getPackageName();
ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
sSharedPrefs.put(packageName, packagePrefs);
}

// At least one application in the world actually passes in a null
// name. This happened to work because when we generated the file name
// we would stringify it to "null.xml". Nice.
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}

sp = packagePrefs.get(name);
if (sp == null) {
File prefsFile = getSharedPrefsFile(name);
sp = new SharedPreferencesImpl(prefsFile, mode);
packagePrefs.put(name, 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;

可见 sdk 是先取了缓存(sSharedPrefs静态变量), 如果缓存未命中, 才构造对象. 也就是说, 多次 getSharedPreferences 几乎是没有代价的. 同时, 实例的构造被 synchronized 关键字包裹, 因此构造过程是多线程安全的
构造SharedPreferences

第一次构建SharedPreferences对象
// SharedPreferencesImpl.java
SharedPreferencesImpl(File file, int www.huarenyl.cn mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
startLoadFromDisk(www.mcyllpt.com);
几个关键类成员信息解释如下
1. mFile 代表我们磁盘上的配置文件
2. mBackupFile 是一个灾备文件, 用户写入失败时进行恢复, 后面会再说. 其路径是 mFile 加后缀 ‘.bak’
3. mMap 用于在内存中缓存我们的配置数据, 也就是 getXxx 数据的来源

重点关注startLoadFromDisk()方法

private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run(www.thd178.com ) {
loadFromDisk(www.taohuayuan178.com );
开启了一个从Disk读取的线程

// SharedPreferencesImpl.java
private void loadFromDisk(www.douniu157.com) {
synchronized (SharedPreferencesImpl.this) {
if (mLoaded) {
return;
}
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}

... 略去无关代码 ...

str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
map = XmlUtils.readMapXml(str);

synchronized (SharedPreferencesImpl.this) {
mLoaded = true;
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();;
}
notifyAll();
loadFromDisk()非常关键,他总共做了以下几件事
1. 如果有 ‘灾备’ 文件, 则直接使用灾备文件回滚.
2. 把配置从磁盘读取到内存的并保存在 mMap 字段中(看代码最后 mMap = map)
3. 标记读取完成, 这个字段后面 awaitLoadedLocked 会用到. 记录读取文件的时间, 后面 MODE_MULTI_PROCESS 中会用到
4. 发一个 notifyAll 通知已经读取完毕, 激活所有等待加载的其他线程

这里写图片描述

getX原理分析

public float getFloat(String key, float defValue) {
synchronized (this) {
awaitLoadedLocked();
Float v = (Float)mMap.get(key);
return v != null ? v : defValue;
关键信息如下:
1. synchronized保证了线程安全
2. get操作一定是从mMap中读取,既从内存中读取,无过多性能损耗。
3. awaitLoadedLocked()保证了读取操作一定在loadFromDisk()执行之完,同步等待。因此第一次调用get操作可能会阻塞,万分注意,这也是sp被定义为轻量级存储系统的重要原因

putX原理分析

put操作较为复杂,一步一步分析

创建editor

// SharedPreferencesImpl.java
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
// Editor, but then we should be able to do:
//
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
synchronized (this) {
awaitLoadedLocked();
}


EditorImpl()无构造函数,仅仅去初始化两个成员变量

// SharedPreferencesImpl.java
public final class EditorImpl implements Editor {
private final Map<String, Object> mModified = Maps.newHashMap();
private boolean mClear = false;

... 略去方法定义 ...
public Editor putString(String key, @Nullable String value) { ... }
public boolean commit() { ... }

关键信息如下:
1. ·mModified 是我们每次 putXxx 后所改变的配置项
2. mClear 标识要清空配置项

putString

public Editor putString(String key, @Nullable String value) {
synchronized (this) {
mModified.put(key, value);
return this;
很简单, 仅仅是把我们设置的配置项放到了 mModified 属性里保存. 等到 apply 或者 commit 的时候回写到内存和磁盘. 咱们分别来看看

apply

// SharedPreferencesImpl.java
public void apply() {
final MemoryCommitResult mcr = commitToMemory();

... 略无关 ...

SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
apply核心在于两点
1. commitToMemory()完成了内存的同步回写
2. enqueueDiskWrite() 完成了硬盘的异步回写, 我们接下来具体看看

// SharedPreferencesImpl.java
private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {

... 略去无关 ...

mcr.mapToWriteToDisk = mMap;
mDiskWritesInFlight++;

synchronized (this) {
for (Map.Entry&lt;String, Object&gt; e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// &quot;this&quot; is the magic value for a removal mutation. In addition,
// setting a value to &quot;null&quot; for a given key is specified to be
// equivalent to calling remove on that key.
if (v == this || v == null) {
mMap.remove(k);
} else {
mMap.put(k, v);
}
}

mModified.clear();
}
}
return mcr;
两个关键信息
1. 把 Editor.mModified 中的配置项回写到 SharedPreferences.mMap 中, 完成了内存的同步
2. 把 SharedPreferences.mMap 保存在了 mcr.mapToWriteToDisk 中. 而后者就是即将要回写到磁盘的数据源

// SharedPreferencesImpl.java
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr);
}

...
}
};

...

QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
关键信息
使用singleThreadExecutor单一线程池去依次执行写入磁盘的runnable序列

之后是真正执行把数据写入磁盘的方法
// SharedPreferencesImpl.java
private void writeToFile(MemoryCommitResult mcr) {
// Rename the current file so it may be used as a backup during the next read
if (mFile.exists()) {
if (!mBackupFile.exists()) {
if (!mFile.renameTo(mBackupFile)) {
return;
}
} else {
mFile.delete();
}
}

// Attempt to write the file, delete the backup and return true as atomically as
// possible. If any exception occurs, delete the new file; next time we will restore
// from the backup.
try {
FileOutputStream str = createFileOutputStream(mFile);
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Os.stat(mFile.getPath());
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
return;
}

// Clean up an unsuccessfully written file
mFile.delete();
主要分为三个过程:
1. 先把已存在的老的配置文件重命名(加 ‘.bak’ 后缀), 然后删除老的配置文件. 这相当于做了灾备
2. 向 mFile 中一次性写入所有配置项. 即 mcr.mapToWriteToDisk(这就是 commitToMemory 所说的保存了所有配置项的字段) 一次性写入到磁盘. 如果写入成功则删除灾备文件, 同时记录了这次同步的时间
3. 如果上述过程 [2] 失败, 则删除这个半成品的配置文件

apply总结

由于apply比较复杂,稍作总结:
1. 通过 commitToMemory 将修改的配置项同步回写到内存 SharedPreferences.mMap 中. 此时, 任何的 getXxx 都可以获取到最新数据了
2. 通过 enqueueDiskWrite 调用 writeToFile 将所有配置项一次性异步回写到磁盘. 这是一个单线程的线程池

这里写图片描述

commit

commit比较简单,直接看代码和时序图即可,大致和apply相同
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
这里写图片描述

注意:commit最后会等待异步任务返回,说明会阻塞当前调用线程,因此说commit是同步写入,apply是异步写入。

以上涵盖了大部分SharedPreferences重要源码分析,下面总结SharedPreferences最佳实践,提出日后要注意的问题,只说结论不解释原因。(如果你不明白为什么,证明你前面的分析没有深刻理解)
SharedPreferences最佳实践

勿存储过大value

永远记住,SharedPreferences是一个轻量级的存储系统,不要存过多且复杂的数据,这会带来以下的问题
第一次从sp中获取值的时候,有可能阻塞主线程,使界面卡顿、掉帧。
这些key和value会永远存在于内存之中,占用大量内存。
勿存储复杂数据

SharedPreferences通过xml存储解析,JSON或者HTML格式存放在sp里面的时候,需要转义,这样会带来很多&这种特殊符号,sp在解析碰到这个特殊符号的时候会进行特殊的处理,引发额外的字符串拼接以及函数调用开销。如果数据量大且复杂,严重时可能导频繁GC。
不要乱edit和apply,尽量批量修改一次提交

edit会创建editor对象,每进行一次apply就会创建线程,进行内存和磁盘的同步,千万写类似下面的代码
SharedPreferences sp = getSharedPreferences("test", MODE_PRIVATE);
sp.edit().putString("test1", "sss").apply();
sp.edit().putString("test2", "sss").apply();
sp.edit().putString("test3", "sss").apply();
sp.edit().putString("test4", "sss").apply();
建议apply,少用commit

commit同步写内存,同步写磁盘。有是否成功的返回值
apply同步写内存,异步写磁盘。无返回值
registerOnSharedPreferenceChangeListener弱引用问题

见本文初
apply和commit对registerOnSharedPreferenceChangeListener的影响

对于 apply, listener 回调时内存已经完成同步, 但是异步磁盘任务不保证是否完成
对于 commit, listener 回调时内存和磁盘都已经同步完毕
不要有任何用SP进行多进程存储的幻想

这个话题不需要过多讨论,只记住一点,多进程别用SP,Android没有对SP在多进程上的表现做任何约束和保证。附上Google官方注释
@deprecated MODE_MULTI_PROCESS does not work reliably in
some versions of Android, and furthermore does not provide any mechanism for reconciling

转载于:https://www.cnblogs.com/qwangxiao/p/8667831.html

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

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

相关文章

【译】《学习JavaScript设计模式》(二)

原书链接Learning JavaScript Design Patterns水平有限很多地方不通顺&#xff0c;错翻漏翻欢迎交流。 模式测试&#xff0c;原生模式&约法三章 记住并不是所有的算法啊&#xff0c;最佳实践啊&#xff0c;解决方案啊什么的都可以被称为一个完整的模式的。很可能它就缺了点…

I.Mx6 使用串口连接PSAM卡的注意事项

&#xff5b;背景&#xff5d; 1. 使用模拟方式与PSAM卡进行通信&#xff0c;对时序的要求非常严格&#xff0c;在自己的代码中一定要做好相关延时尤其是每个etu的时间 要测量准确。 2. 使用串口方式与PSAM卡直连&#xff0c;是硬件级的通信&#xff0c;写起来相对容易一些。 &…

CentOS7.0 安装 tomcat-9.0

2019独角兽企业重金招聘Python工程师标准>>> 1、解压 # tar -zxvf apache-tomcat-9.0.0.M4.tar.gz -C /opt/usr/local 改个名字好以后操作&#xff1a; # mv apache-tomcat-9.0.0.M4.tar.gz tomcat 2、启动&停止 # /opt/usr/local/tomcat/bin/start…

SpringMVC转发和重定向

页面间的跳转分为转发和重定向 但是转发和重定向在springMVC里又分为转发到页面和重定向到页面&#xff0c;转发到处理器方法和重定向到处理器方法 下边是具体实现功能的代码&#xff1a; 转发到页面和重定向到页面 //转发到页面RequestMapping("/five")public Strin…

一幅长文细学华为MRS大数据开发(四)——HBase

4 HBase 摘要&#xff1a;HBase是一种非关系型数据库&#xff0c;它是基于谷歌BigTable的开源实现&#xff0c;和BigTable一样&#xff0c;支持大规模海量数据的存储&#xff0c;对于分布式并发数据处理的效率极高&#xff0c;易于扩展且支持动态伸缩&#xff0c;适用于廉价设备…

AOP技术基础

1、引言2、AOP技术基础 3、Java平台AOP技术研究4、.Net平台AOP技术研究2.1 AOP技术起源 AOP技术的诞生并不算晚&#xff0c;早在1990年开始&#xff0c;来自Xerox Palo Alto Research Lab&#xff08;即PARC&#xff09;的研究人员就对面向对象思想的局限性进行了分析。他们研…

Could not find a package,configuration file provided by G2O ,G2OConfig.cmake,g2o-config.cmake

因为项目需要使用到g2o&#xff0c;所以自己从git上面clone下来&#xff0c; git clone https://github.com/RainerKuemmerle/g2o.git 然后&#xff1a; cd g2o mkdir build cd build cmake .. make -j4 编译完成&#xff0c;并在CMakeLists.txt使用g2o&#xff1a; set(G2O_DI…

TCP 协议的三次握手、四次分手

详细描述了 TCP 协议的连接和关闭的整个过程。解释了为什么 TCP 协议是面向连接的、可靠的数据传输协议。 TCP 在互联网上之间的通信交流&#xff0c;一般是基于 TCP (Transmission Control Protocol&#xff0c;传输控制协议) 或者 UDP (User Datagram Protocol&#xff0c;用…

prefix.pch文件的一些简单使用

该文件可以引入一些全局通用的文件&#xff0c;这样就不用每次在文件中导入&#xff0c;也可以定义全局通用的变量或常量&#xff0c;如 //定义NSString常量 #define ServiceName "cn.zmfc" //定义颜色 #define BackgroudColor [UIColor colorWithRed:232/2…

[Bzoj4182]Shopping(点分治)(树上背包)(单调队列优化多重背包)

4182: Shopping Time Limit: 30 Sec Memory Limit: 128 MBSubmit: 374 Solved: 130[Submit][Status][Discuss]Description 马上就是小苗的生日了&#xff0c;为了给小苗准备礼物&#xff0c;小葱兴冲冲地来到了商店街。商店街有n个商店&#xff0c;并且它们之间的道路构成了一…

您不能在64-位可执行文件上设置DEP属性?

我是为dllhost.exe设置DEP时遇到了同样的情况。你需要选择64位系统对应的程序。64位系统&#xff1a;C:\Windows\SysWOW64\dllhost.exe32位系统&#xff1a;C:\Windows\System32\dllhost.exe

应聘者提问环节

http://blog.csdn.net/jinhuiyu/article/details/4487058转载于:https://www.cnblogs.com/wuchanming/p/4333791.html

Tiny框架2.0版火热推出

方法论 方法论决定了可以达到的高度 方法论&#xff0c;就是人们认识世界、改造世界的根本方法。 它是人们用什么样的方式、方法来观察事物和处理问题。概括地说&#xff0c;世界观主要解决世界“是什么”的问题&#xff0c;方法论主要解决“怎么办”的问题。 方法论是一种以解…

Java--Socket通信

下面内容是Java开发内容的高级知识点&#xff0c;需要对Java中的面向对象、IO、多线程、以及网络相关知识有一定的基础。(知识永远都有深度&#xff0c;本章节长期更新内容) 1、网络基础知识 网络通信的条件&#xff1a;1、两个通信的端都要有各自的IP地址作为唯一标识&#xf…

一幅长文细学华为MRS大数据开发(五)——MapReduce和Yarn

5 MapReduce和Yarn 摘要&#xff1a;本文中主要讲述大数据领域中最著名的批处理和离线处理计算框架——MapReduce&#xff0c;包括MapReduce的原理、流程、使用场景&#xff0c;以及Hadoop集群中负责统一的资源管理和调度的组件——Yarn。 作者&#xff1a;来自ArimaMisaki创作…

数据:ContentResolver类

ContentResolver是通过URI来查询ContentProvider中提供的数据。除了URI以 外&#xff0c;还必须知道需要获取的数据段的名称&#xff0c;以及此数据段的数据类型。 如果你需要获取一个特定的记录&#xff0c;你就必须知道当前记录的ID。 简要介绍ContentResolver的主要接口&…

[MySQL 5.6] Performance Schema 之 PS配置项(1)

尽管Performance Schema&#xff08;以下简称PS&#xff09;在5.5中已经出现&#xff0c;但一直没有使用过&#xff0c;并且相比5.6&#xff0c;5.5的PS表要少很多。 以下从一个初学者的角度&#xff0c;阅读PS的官方文档&#xff0c;做一些简单的笔记官方文档见&#xff1a;ht…

Tensorflow从入门到精通之——Tensorflow基本操作

前边的章节介绍了什么是Tensorflow&#xff0c;本节将带大家真正走进Tensorflow的世界&#xff0c;学习Tensorflow一些基本的操作及使用方法。同时也欢迎大家关注我们的网站和系列教程&#xff1a;http://www.tensorflownews.com/&#xff0c;学习更多的机器学习、深度学习的知…

一幅长文细学Vue(七)——路由

7 路由 摘要&#xff1a;在本文中我们会谈及路由的知识点&#xff0c;了解前端路由工作的过程以及如何在Vue3中配置路由&#xff0c;知道怎么使用嵌套路由和实现动态路由匹配&#xff0c;以及使用编程式导航和导航守卫。 声明&#xff1a;为了文章的清爽性&#xff0c;在文章内…

[编程题] 按照左右半区的方式重新组合单链表

[编程题] 按照左右半区的方式重新组合单链表 给定一个单链表的头部节点head&#xff0c;链表长度为N。 如果N为偶数&#xff0c;那么前N/2个节点算作左半区&#xff0c;后N/2个节点算作右半区&#xff1b; 如果N为奇数&#xff0c;那么前N/2个节点算作左半区&#xff0c;后N/21…