转载注明出处:简书-十个雨点
开发一个多进程应用的时候,我们往往无法避免在多个进程之间共享数据。
多进程共享数据的方法有很多种,在Android中常用的有:SharedPreferences(多进程模式)、广播、Socket、ContentProvider、Messenger、AIDL等。这些方法适用于不同的使用场景,又有各自的局限性。
本文即将介绍的是通过ContentProvider,结合SharedPreferences(以下简称SP)实现的进程间共享设置项的功能。这种方式主要适用于以下场景:在一个进程中进行一些设置,而需要在另一个进程实时读取设置,并根据这些设置来执行功能。
1.SharedPreferences在多进程共享下的局限
有些同学可能会觉得奇怪:明明上面才说了SP(多进程模式)也是多进程共享数据的方法,为什么还需要通过ContentProvider来做呢。答案很简单,因为SP其实并不能保证多进程间同步,下图是关于MODE_MULTI_PROCESS的注释,我就不全文翻译了。
MODE_MULTI_PROCESS模式的注释
我们都知道,SP其实是将key-value对保存在手机的data/data/you.package.name/shared_prefs/目录下的文件中。为了减少IO造成的性能损失,SP使用了缓存的机制,会先把数据保存在内存中,在读取的时候直接从内存中读取,而写的时候才会保存到文件。也就是说普通SP是一次读取,多次写入的工作模式。所以如果多个进程中都使用了普通的SP,分别进行保存就会导致相互覆盖。而设置了MODE_MULTI_PROCESS之后,在多进程使用的时候,会在检测到文件变化的时候重新加载文件到内存中,这样虽然损失了一部分性能,但是却部分实现了多进程间同步。
为什么说是"部分"实现了多进程间同步呢?因为在频繁进行SP操作的时候,就还是会出现相互覆盖的问题。我初步估计是因为两个进程同时进行了文件的写操作,带着这个猜测我阅读了源码。
先看看对MODE_MULTI_PROCESS是如何处理的
ContextImpl的getSharedPreferences方法:
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
checkMode(mode);
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap 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的startReloadIfChangedUnexpectedly方法:
void startReloadIfChangedUnexpectedly() {
synchronized (this) {
// TODO: wait for any pending writes to disk?
if (!hasFileChangedUnexpectedly()) {
return;
}
startLoadFromDisk();
}
}
可见遇到MODE_MULTI_PROCESS的时候,会强制让SP进行一次读取操作,从而保证数据是最新的。因此如果你在外部保存了一份SP的对象,反而会导致享受不到MODE_MULTI_PROCESS带来的同步效果了,而且从源码中可