SharedPreference 是一个 Android 开发自带的适合保存轻量级数据的 K-V 存储库,它使用了 XML 的方式来存储数据,比如我就经常用它保存一些如用户登录信息等轻量级数据。那么今天就让我们来分析一下它的源码,研究一下其内部实现。
获取SharedPreferences
我们在使用 SharedPreferences 时首先是需要获取到这个 SharedPreferences 的,因此我们首先从 SharedPreferences 的获取入手,来分析其源码。
根据名称获取 SP
不论是在 Activity 中调用 getPreferences() 方法还是调用 Context 的 getSharedPreferences 方法,最终都是调用到了 ContextImpl 的 getSharedPreferences(String name, int mode) 方法。我们先看看它的代码:
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// 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";
}
}
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
可以看到,它首先对 Android 4.4 以下的设备做了特殊处理,之后将对 mSharedPrefsPaths 的操作加了锁。mSharedPrefsPaths 的声明如下:
private ArrayMap<String, File> mSharedPrefsPaths;
可以看到它是一个以 name 为 key,name 对应的 File 为 value 的 HashMap。首先调用了 getSharedPreferencesPath 方法构建出了 name 对应的 File,将其放入 map 后再调用了 getSharedPreferences(File file, int mode) 方法。
获取 SP 名称对应的 File 对象
我们先看看是如何构建出 name 对应的 File 的。
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
可以看到,调用了 makeFilename 方法来创建一个名为 name.xml 的 File。makeFilename 中仅仅是做了一些判断,之后 new 出了这个 File 对象并返回。
可以看到,SharedPreference 确实是使用 xml 来保存其中的 K-V 数据的,而具体存储的路径我们这里就不再关心了,有兴趣的可以点进去看看。
根据创建的 File 对象获取 SP
我们接着看到获取到 File 并放入 Map 后调用的 getSharedPreferences(file, mode) 方法:
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); // 1
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");
}
}
sp = new SharedPreferencesImpl(file, mode); // 2
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;
}
首先可以看到注释 1 处,这里调用了 getSharedPreferencesCacheLocked 来获取到了一个 ArrayMap<File, SharedPreferencesImpl>,之后再从这个 Map 中尝试获取到对应的 SharedPreferencesImpl 实现类(简称 SPI)。
之后看到注释 2 处,当获取不到对应 SPI 时,再创建一个对应的 SPI,并将其加入这个 ArrayMap 中。
这里很明显是一个缓存机制的实现,以加快之后获取 SP 的速度,同时可以发现,SP 其实只是一个接口,而 SPI 才是其具体的实现类。
缓存机制
那么我们先来看看其缓存机制,进入 getSharedPreferencesCacheLocked 方法: