[TOC]
## 使用
### 获取SharedPreferences的两种方式
1. 调用Context对象的getSharedPreferences()方法
2. 调用Activity对象的getPreferences()方法
两种方式的区别://待考证
1. 调用Context对象的getSharedPreferences()方法获得的SharedPreferences对象可以被同一应用程序下的其他组件共享.
2. 调用Activity对象的getPreferences()方法获得的SharedPreferences对象只能在该Activity中使用.
### SharedPreferences的四种操作模式:
* Context.MODE\_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容
* Context.MODE\_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件.
* Context.MODE\_WORLD\_READABLE和Context.MODE\_WORLD\_WRITEABLE用来控制其他应用是否有权限读写该文件.
* MODE\_WORLD\_READABLE:表示当前文件可以被其他应用读取. Android N后不支持
* MODE\_WORLD\_WRITEABLE:表示当前文件可以被其他应用写入.Android N后不支持
## 源码解析
### ContextImpl
~~~
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
private ArrayMap<String, File> mSharedPrefsPaths;
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
...
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
//获取文件路径=当前app的data目录下的shared_prefs目录+"/"+SharePreferences的name+“.xml”
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
//读取缓存
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
// 如果缓存中不存在 SharedPreferences 对象
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.
// 如果由其他进程修改了这个 SharedPreferences 文件,我们将会重新加载它
sp.startReloadIfChangedUnexpectedly();
}
retu
~~~
* 缓存未命中, 才构造 SharedPreferences 对象,也就是说,多次调用 getSharedPreferences 方法并不会对性能造成多大影响,因为又缓存机制
* SharedPreferences 对象的创建过程是线程安全的,因为使用了 synchronize 关键字
* 如果命中了缓存,并且参数 mode 使用了 Context.MODE\_MULTI\_PROCESS ,那么将会调用 sp.startReloadIfChangedUnexpectedly() 方法,在 startReloadIfChangedUnexpectedly 方法中,会判断是否由其他进程修改过这个文件,如果有,会重新从磁盘中读取文件加载数据
### SharedPreferencesImpl
#### 初始化
~~~
SharedPreferencesImpl(File file, int mode) {
//文件
mFile = file;
//回滚文件
mBackupFile = makeBackupFile(file);
//模式
mMode = mode;
//是否加载
mLoaded = false;
//key/value 缓存
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);
}
}
// Debugging
if (mFile.exists() && !mFile.canRead()) {
Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
}
Map<String, Object> map = null;
//文件详情 如编辑时间等
StructStat stat = null;
Throwable thrown = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
//将文件读取到
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) {
} catch (Throwable t) {
thrown = t;
}
synchronized (mLock) {
// 加载数据成功,设置 mLoaded 为 true
mLoaded = true;
mThrowable = thrown;
// It's important that we always signal waiters, even if we'll make
// them fail with an exception. The try-finally is pretty wide, but
// better safe than sorry.
try {
if (thrown == null) {
if (map != null) {
// 将解析得到的键值对数据赋值给 mMap
mMap = map;
// 将文件的修改时间戳保存到 mStatTimestamp 中
mStatTimestamp = stat.st_mtim;= map;
// 将文件的大小保存到 mStatSize
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
}
// In case of a thrown exception, we retain the old map. That allows
// any open editors to commit and store updates.
} catch (Throwable t) {
mThrowable = t;
} finally {
//通知等待
mLock.notifyAll();
~~~
* 如果有备份文件,直接使用备份文件进行回滚
* 第一次调用 getSharedPreferences 方法的时候,会从磁盘中加载数据,而数据的加载时通过开启一个子线程调用 loadFromDisk 方法进行异步读取的
* 将解析得到的键值对数据保存在 mMap 中
* 将文件的修改时间戳以及大小分别保存在 mStatTimestamp 以及 mStatSize 中(保存这两个值有什么用呢?我们在分析 getSharedPreferences 方法时说过,如果有其他进程修改了文件,并且 mode 为 MODE\_MULTI\_PROCESS ,将会判断重新加载文件。如何判断文件是否被其他进程修改过,没错,根据文件修改时间以及文件大小即可知道)
* 调用 notifyAll() 方法通知唤醒其他等待线程,数据已经加载完毕
#### get
~~~
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
//等待加载完毕
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
@Override
@Nullable
public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
synchronized (mLock) {
awaitLoadedLocked();
Set<String> v = (Set<String>) mMap.get(key);
return v != null ? v : defValues;
}
}
private void awaitLoadedLocked() {
if (!mLoaded) {
// 严苛模式
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
}
~~~
getString 方法代码很简单,其他的例如 getInt , getFloat 方法也是一样的原理,我们直接对这个疑问进行总结:
* getXxx 方法是线程安全的,因为使用了 synchronize 关键字
* getXxx 方法是直接操作内存的,直接从内存中的 mMap 中根据传入的 key 读取 value
* getXxx 方法有可能会卡在 awaitLoadedLocked 方法,从而导致线程阻塞等待( **什么时候会出现这种阻塞现象呢?前面我们分析过,第一次调用 ****getSharedPreferences**** 方法时,会创建一个线程去异步加载数据,那么假如在调用完 ****getSharedPreferences**** 方法之后立即调用 ****getXxx**** 方法,此时的 ****mLoaded**** 很有可能为 ****false**** ,这就会导致 ****awaiteLoadedLocked**** 方法阻塞等待,直到 ****loadFromDisk**** 方法加载完数据并且调用 ****notifyAll**** 来唤醒所有等待线程 **)
#### SharedPreferencesImpl.edit()
~~~
public Editor edit() {
synchronized (this) {//同步方法,保证每次只有一个线程执行加载操作
awaitLoadedLocked();//等待SharedPreferencesImpl加载到内存中,见2.7
}
return new EditorImpl();//创建一个新的Editor实现。
}
~~~
#### EditorImpl
~~~
public final class EditorImpl implements Editor {
private final Object mLock = new Object();
@GuardedBy("mLock")
private final Map<String, Object> mModified = Maps.newHashMap();
@GuardedBy("mLock")
private boolean mClear = false;
public Editor putString(String key, @Nullable String value) {
synchronized (mLock) {
mModified.put(key, value);
return this;
}
~~~
维护一个Map
#### EditorImpl.commit()
todo
- Android
- 四大组件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介绍
- MessageQueue详细
- 启动流程
- 系统启动流程
- 应用启动流程
- Activity启动流程
- View
- view绘制
- view事件传递
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大数据
- Binder小结
- Android组件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 迁移与修复
- Sqlite内核
- Sqlite优化v2
- sqlite索引
- sqlite之wal
- sqlite之锁机制
- 网络
- 基础
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP进化图
- HTTP小结
- 实践
- 网络优化
- Json
- ProtoBuffer
- 断点续传
- 性能
- 卡顿
- 卡顿监控
- ANR
- ANR监控
- 内存
- 内存问题与优化
- 图片内存优化
- 线下内存监控
- 线上内存监控
- 启动优化
- 死锁监控
- 崩溃监控
- 包体积优化
- UI渲染优化
- UI常规优化
- I/O监控
- 电量监控
- 第三方框架
- 网络框架
- Volley
- Okhttp
- 网络框架n问
- OkHttp原理N问
- 设计模式
- EventBus
- Rxjava
- 图片
- ImageWoker
- Gilde的优化
- APT
- 依赖注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 协程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 运行期Java-hook技术
- 编译期hook
- ASM
- Transform增量编译
- 运行期Native-hook技术
- 热修复
- 插件化
- AAB
- Shadow
- 虚拟机
- 其他
- UI自动化
- JavaParser
- Android Line
- 编译
- 疑难杂症
- Android11滑动异常
- 方案
- 工业化
- 模块化
- 隐私合规
- 动态化
- 项目管理
- 业务启动优化
- 业务架构设计
- 性能优化case
- 性能优化-排查思路
- 性能优化-现有方案
- 登录
- 搜索
- C++
- NDK入门
- 跨平台
- H5
- Flutter
- Flutter 性能优化
- 数据跨平台