概述
SharedPreferences(简称SP)是Android中常用的数据存储方式,SP采用key-value(键值对)形式,主要用于轻量级的数据存储,尤其适合保存应用的配置参数,但不建议使用SP来存储大规模的数据,可能会降低性能。
SP采用XML文件格式来保存数据,该文件位于 /data/data/<packageName>/shared_prefs/
。
使用示例
// 加载SP文件数据,“my_prefs”为文件名
SharedPreferences sp = getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
// 保存数据
Editor editor = sp.edit();
editor.putString("blog", "www.xiaox.com");
// 提交数据:同步方式,有返回值表示数据保存是否成功
boolean result = editor.commit();
// 提交数据:异步方式,没有返回值//
editor.apply()
// 读取数据
String blog = sp.getString("blog", "");
my_prefs.xml文件内容:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="blog">www.xiaox.com</string>
</map>
架构
类图
说明:SharedPreferences与Editor只是两个接口,SharedPreferencesImpl和EditorImp分别实现了对应的接口。另外,ContextImpl记录着SharedPreferences的重要数据,如下:
- sSharedPrefsCache:以包名为key,二级key是SP文件,以SharedPreferencesImp为value的嵌套map结构,sSharedPrefsCache是静态成员变量,每个进程只有唯一的一份,且由ContextImpl.class锁保护。
- mSharedPrefsPaths:记录所有的SP文件,以文件名为key,具体文件为value的map结构。
- mPreferencesDir:是值SP所在目录,即
/data/data/<packageName>/shared_prefs/
工作流程
说明:
- putXxx()操作:把数据写入到EditorImpl.mModified;
- apply()或者commit()操作: a. 先调用
commitToMemory()
,将数据同步到SharedPreferencesImpl的mMap,并保存到MemoryCommitResult的mapToWriteToDisk
; b. 再调用enqueueDiskWrite()
,写入到磁盘文件;在这之前把原有数据保存到.bak
后缀的文件,用于在写磁盘的过程出现任何异常可恢复数据。 - getXxx()操作:从SharedPreferencesImpl.mMap读取数据。
源码分析(API 28)
获取SharedPreferences
可以通过 Activity.getPreferences(mode)
、 PreferenceManager.getDefaultSharedPreferences(context)
或者 Context.getSharedPreferences(name,mode)
来获取SharedPreferences实例, 最终调用的是ContextImpl的getSharedPreferences(name, mode)。
ContextImpl#getSharedPreferences(name, mode):
class ContextImpl extends Context {
@GuardedBy("ContextImpl.class")
private ArrayMap<String, File> mSharedPrefsPaths;
// ...
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// ...
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
// 先从mSharedPrefsPaths查询是否存在相应文件
file = mSharedPrefsPaths.get(name);
if (file == null) {
// 如果文件不存在,则创建新的文件
file = getSharedPreferencesPath(name);
// 将新创建的文件保存到mSharedPrefsPaths,以文件名为key
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
// 创建目录/data/data/<packageName>/shared_prefs/
mPreferencesDir = new File(getDataDir(), "shared_prefs");
}
return ensurePrivateDirExists(mPreferencesDir);
}
}
}
ContextImpl#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) {
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)