一、引言
SharedPreferences使用方法非常简单,是Android开发中使用频率非常高的轻量级存储方式,经常被用来存放一些键值对数据,比如配置信息等,得到了很多很多开发者的青睐。但是SharedPreferences并不是万能的,如果使用不当,会造成严重的后果,下面记录一个工作中遇到的实例。
二、数据丢了
之前项目中为了实现Service保活,使用了多进程的方式,Service开了一个新的进程,程序里面很多配置信息都是用的SharedPreferences实现的,并且读写操作都有。通过查看google官方文档可以知道,SharedPreferences提供了跨进程的模式MODE_MULTI_PROCESS
This constant was deprecated in API level 23.
MODE_MULTI_PROCESS does not work reliably in some versions of Android, and furthermore does not provide any mechanism for reconciling concurrent modifications across processes. Applications should not attempt to use it. Instead, they should use an explicit cross-process data management approach such as ContentProvider.
官方文档里面这种跨进程方式在某些Android版本上不可靠,并且未来也不会提供任何支持,要是用跨进程数据传输需要使用类似ContentProvider的东西。考虑到当时使用Android6.0的设备占比很低,而且由于项目里面很多地方都使用了SharedPreferences,换一种方案代价会比较大,最终还是抱着试试的态度直接通过设置MODE_MULTI_PROCESS这种模式在多进程里面使用SharedPreferences读写数据了。
然而,结果很悲催,测试发现时不时就会出现数据丢失的情况。无果,只能看源码了
三、神奇的mode
在上面的实例中,使用了MODE_MULTI_PROCESS这种模式来操作SharedPreferences,结果发现多进程中出现了数据丢失的情况,说明在多进程中,SharedPreferences的MODE_MULTI_PROCESS并不能实现进程安全。既然如此,看下MODE_MULTI_PROCESS到底做了什么。
Context是一个抽象类,其实现是ContextImpl,去到ContextImpl类里面的getSharedPreference方法
public SharedPreferences getSharedPreferences(File file, int mode) {
checkMode(mode);
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
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.
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
再去到SharedPreferencesImpl里面
void startReloadIfChangedUnexpectedly() {
synchronized (this) {
// TODO: wait for any pending writes to disk?
if (!hasFileChangedUnexpectedly()) {
return;
}
startLoadFromDisk();
}
}
private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
通过源码可以看到这个mode用于API11以前的系统上,次获取这个sp的时候,如果有这个mode,会重新读一遍文件,而且后续读取过程并没有再用到MODE_MULTI_PROCESS这个mode。所以,如果这个mode并没有用来处理进程安全,如果多进程中使用,必然是会出现上面实例中的时不时丢数据的情况。