SharedPreferense 实现原理
简介
SharedPreferences是Android提供给我们的用于存储轻量级K-V数据的持久化方案。以XML文件的形式存储在/data/data/packageName/的shared_prefs文件夹。
它提供了 putString()、putString(Set)、putInt()、putLong()、putFloat()、putBoolean() 六种数据类型。(注意没有Double)
使用示例
//根据文件名,获取SharedPreferences对象;mode一般都使用MODE_PRIVATE,只能由该App访问
SharedPreferences sp = context.getSharedPreferences("setting", Context.MODE_PRIVATE)
//根据key,获取指定值
Boolean needInitChannels = sp.getBoolean("isDebug", false)
//获取Editor编辑对象,用于编辑SharedPreferences
SharedPreferences.Editor editor = sp.edit()
editor.putBoolean("isDebug",true)
editor.putLong("isLong",1000)
//同步提交到SharedPreferences文件,获取是否同步成功的结果
Boolean res = editor.commit()
//异步提交到SharedPreferences文件
editor.apply()
当我们第一次访问一个名为"setting"的SharedPreferences文件,系统会在应用数据目录下(/data/data/packageName/)的shared_prefs文件夹下,创建一个同名的setting.xml文件。
存储的xml文件格式如下:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<long name="isLong" value="1000" />
<boolean name="isDebug" value="true" />
<!-- <float name="isFloat" value="1.5" />
<string name="isString">Android</string>
<int name="isInt" value="1" />
<set name="isStringSet">
<string>element 1</string>
<string>element 2</string>
<string>element 3</string>
</set> -->
</map>
原理分析
初始化
我们在使用SP之前会先通过context.getSharedPreferences()获取SP的实例对象, context的实现类是ContextImpl, 看下ContextImpl的getSharedPreferences实现:
public SharedPreferences getSharedPreferences(String name, int mode) {
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null"; //name为null,则文件命名为null.xml
}
}
File file;
synchronized (ContextImpl.class) { //加锁同步
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);
//mSharedPrefsPaths缓存文件名和文件映射
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
这里有一个重要的参数mSharedPrefsPaths
private ArrayMap<String, File> mSharedPrefsPaths;
它是一个ArrayMap,缓存了文件名和文件对象的映射。初始化获取时会先从缓存里获取对应的文件对象,没有再去创建文件并缓存。
接着通过getSharedPreferences(file, mode)获取SharedPreferences对象:
@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);
// ... new一个实例
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
//.....
return sp;
}
同样可以看到,这里对SharedPreferences的实例对象SharedPreferencesImpl也进行了缓存。
getSharedPreferences获取缓存:
@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;
}
sSharedPrefsCache时ContextImpl的静态变量,缓存了packageName-ArrayMap<File, SharedPreferencesImpl>
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
也就是说sSharedPrefsCache缓存了同一个应用包名的ArrayMap<File, SharedPreferencesImpl>集合,一个文件对应一个SharedPreferencesImpl对象。
也就是说,一个name会对应一个SharedPreferences的File实例,而一个File会对应一个SharedPreferencesImpl实例。并且对File实例和SharedPreferencesImpl实例对象都进行了缓存
首次使用 getSharedPreferences 时,内存中不存在 SP 以及 SP Map 缓存,需要创建 SP 并添加到 ContextImpl 的静态成员变量(sSharedPrefs)中。
sp = new SharedPreferencesImpl(file, mode);
SharedPreferencesImpl构造方法
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak");
}
makeBackupFile 用来定义备份文件,命名为 “xml同名.bak”, 该文件在写入磁盘时会用到,用来备份文件,在写入失败异常的情况下,下次使用从备份文件恢复,这样就只需丢弃写入失败的数据,而之前的数据还能恢复。
@UnsupportedAppUsage
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
//开启异步线程从磁盘读取文件,加锁防止多线程并发操作
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
private void loadFromDisk() {
synchronized (mLock) { //加锁
if (mLoaded) { //已经加载过
return;
}
//备份文件存在,说明上次写入失败,直接从备份文件恢复到mFile
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(m