一、前言
SharedPreferences 通过读写磁盘xml文件的方式,为客户端提供便捷的键值对持久化服务。同时支持同步和异步两种数据提交方式,减少对主线程运行的影响。
虽然此工具类因使用方便深得开发者的青睐,但其多线程操作、多进程操作是否安全的问题,却鲜有人探究。对 SharedPreferences 存取操作感兴趣的读者,这里先为您呈上文章 SharedPreferences。
接下来透过进程启动流程,得出主题结论。因为涉及 ActivityThread、ApplicationThread、ActivityManagerService、Android IPC等知识,请自行查阅,本文不再赘述。
本文源码来自 Android 23。
二、ActivityThread
省略前面源Activity检查和创建操作,直到新应用的 ActivityThread 把 ApplicationThread 注册到 ActivityManagerService。注册完成后 ActivityManagerService 通过IPC调用 IApplicationThread.bindApplication(…)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
.....
try {
.....
// 经过上述处理后,通过IPC调用ApplicationThread.bindApplication()
thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked());
updateLruProcessLocked(app, false, null);
app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
} catch (Exception e) {
// todo: Yikes! What should we do? For now we will try to
// start another process, but that could easily get us in
// an infinite loop of restarting processes...
Slog.wtf(TAG, "Exception thrown during bind of " + app, e);
app.resetPackageList(mProcessStats);
app.unlinkDeathRecipient();
startProcessLocked(app, "bind fail", processName);
return false;
}
.....
return true;
}
实现方法是 ActivityThread.ApplicationThread.bindApplication()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public final void bindApplication(String processName, ApplicationInfo appInfo,
List<ProviderInfo> providers, ComponentName instrumentationName,
ProfilerInfo profilerInfo, Bundle instrumentationArgs,
IInstrumentationWatcher instrumentationWatcher,
IUiAutomationConnection instrumentationUiConnection, int debugMode,
boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
Bundle coreSettings) {
.....
IPackageManager pm = getPackageManager();
android.content.pm.PackageInfo pi = null;
try {
pi = pm.getPackageInfo(appInfo.packageName, 0, UserHandle.myUserId());
} catch (RemoteException e) {
}
if (pi != null) {
boolean sharedUserIdSet = (pi.sharedUserId != null);
boolean processNameNotDefault =
(pi.applicationInfo != null &&
!appInfo.packageName.equals(pi.applicationInfo.processName));
boolean sharable = (sharedUserIdSet || processNameNotDefault);
// 除非应用是个共享进程,否则把应用信息告诉VMRuntime
if (!sharable) {
VMRuntime.registerAppInfo(appInfo.packageName, appInfo.dataDir,
appInfo.processName);
}
}
// 构建AppBindData
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableOpenGlTrace = enableOpenGlTrace;
data.restrictedBackupMode = isRestrictedBackupMode;
data.persistent = persistent;
data.config = config;
data.compatInfo = compatInfo;
data.initProfilerInfo = profilerInfo;
// ApplicationThread内通过Handler发送消息到ActivityThread
sendMessage(H.BIND_APPLICATION, data);
}
ActivityThread 的 Handler 实现类 H 接收 BIND_APPLICATION 指令并执行逻辑,这个 Handler 和对应的 Looper 组件就是常说的主线程消息队列,运行在主线程上。
1
private class H extends Handler
回调 H 的方法 handleMessage(Message msg)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void handleMessage(Message msg) {
switch (msg.what) {
.....
case BIND_APPLICATION:
// 从Message取出传送的对象
AppBindData data = (AppBindData)msg.obj;
// 调用方法
handleBindApplication(data);
break;
.....
}
}
从上面的逻辑开始调用 ActivityThread 的 handleBindApplication(data)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// ActivityThread的成员变量mInstrumentation
Instrumentation mInstrumentation;
private void handleBindApplication(AppBindData data) {
mBoundApplication = data;
mConfiguration = new Configuration(data.config);
mCompatConfiguration = new Configuration(data.config);
mProfiler = new Profiler();
.....
// 创建ContextImpl实例
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
if (!Process.isIsolated()) {
final File cacheDir = appContext.getCacheDir();
if (cacheDir != null) {
// Provide a usable directory for temporary files
System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
} else {
Log.v(TAG, "Unable to initialize \"java.io.tmpdir\" property due to missing cache directory");
}
// Use codeCacheDir to store generated/compiled graphics code
final File codeCacheDir = appContext.getCodeCacheDir();
if (codeCacheDir != null) {
setupGraphicsSupport(data.info, codeCacheDir);
} else {
Log.e(TAG, "Unable to setupGraphicsSupport due to missing code-cache directory");
}
}
.....
if (data.instrumentationName != null) {
InstrumentationInfo ii = null;
try {
// 获取InstrumentationInfo
ii = appContext.getPackageManager().
getInstrumentationInfo(data.instrumentationName, 0);
} catch (PackageManager.NameNotFoundException e) {
}
if (ii == null) {
throw new RuntimeException(
"Unable to find instrumentation info for: "
+ data.instrumentationName);
}
mInstrumentationPackageName = ii.packageName;
mInstrumentationAppDir = ii.sourceDir;
mInstrumentationSplitAppDirs = ii.splitSourceDirs;
mInstrumentationLibDir = ii.nativeLibraryDir;
mInstrumentedAppDir = data.info.getAppDir();
mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
mInstrumentedLibDir = data.info.getLibDir();
ApplicationInfo instrApp = new ApplicationInfo();
instrApp.packageName = ii.packageName;
instrApp.sourceDir = ii.sourceDir;
instrApp.publicSourceDir = ii.publicSourceDir;
instrApp.splitSourceDirs = ii.splitSourceDirs;
instrApp.splitPublicSourceDirs = ii.splitPublicSourceDirs;
instrApp.dataDir = ii.dataDir;
instrApp.nativeLibraryDir = ii.nativeLibraryDir;
LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
appContext.getClassLoader(), false, true, false);
ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
try {
// 获取类加载器
java.lang.ClassLoader cl = instrContext.getClassLoader();
// 通过反射创建Instrumentation实例
// 类名由data.instrumentationName.getClassName()指定
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate instrumentation "
+ data.instrumentationName + ": " + e.toString(), e);
}
// 上面创建的ContextImpl实例引用保存在Instrumentation
// 每个进程有一个ActivityThread,其中只有一个Instrumentation实例
// 可知ContextImpl在一个进程里只有一个实例,所以进程能控制其线程安全
mInstrumentation.init(this, instrContext, appContext,
new ComponentName(ii.packageName, ii.name),data.instrumentationWatcher,
data.instrumentationUiAutomationConnection);
.....
} else {
mInstrumentation = new Instrumentation();
}
.....
}
三、Instrumentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Instrumentation {
// 用于线程同步的实例
private final Object mSync = new Object();
// 持有ActivityThread
private ActivityThread mThread = null;
// 从ActivityThread的线程Looper获得MessageQueue
private MessageQueue mMessageQueue = null;
private Context mInstrContext;
// ApplicationContext,就是ContextImpl的实例
private Context mAppContext;
private ComponentName mComponent;
private Thread mRunner;
private List<ActivityWaiter> mWaitingActivities;
private List<ActivityMonitor> mActivityMonitors;
private IInstrumentationWatcher mWatcher;
private IUiAutomationConnection mUiAutomationConnection;
private boolean mAutomaticPerformanceSnapshots = false;
private PerformanceCollector mPerformanceCollector;
private Bundle mPerfMetrics = new Bundle();
private UiAutomation mUiAutomation;
.....
// 实例创建后通过给成员变量赋值
final void init(ActivityThread thread,
Context instrContext, Context appContext, ComponentName component,
IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection) {
mThread = thread;
mMessageQueue = mThread.getLooper().myQueue();
mInstrContext = instrContext;
mAppContext = appContext;
mComponent = component;
mWatcher = watcher;
mUiAutomationConnection = uiAutomationConnection;
}
}
四、ContextImpl
持有一个静态哈希表,键为应用全路径名,值为全路径名对应的 SharedPreferences 实例。该包路径实例不存在就初始化新实例,否则从哈希表中获取实例。
由于 getSharedPreferences 内部把 ContextImpl.class 类实例作为锁对象,所以每次获取指定包路径对应实例都是线程安全。
根据类签名可知 ContextImpl 是 Context 的具体实现类,为 Activity 和其他应用组件提供基础上下文。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class ContextImpl extends Context {
.....
// 包名和对应已缓存的SharedPreferencesImpl实例
private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs;
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
// 把ContextImpl的类作为锁对象
synchronized (ContextImpl.class) {
// 缓存用的哈希表为空则创建该对象
if (sSharedPrefs == null) {
sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
}
// 获取应用的包名
final String packageName = getPackageName();
// 检查应用的包名是否已经缓存对应实例
ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
// 为空则需要为该应用包名创建新实例
if (packagePrefs == null) {
packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
sSharedPrefs.put(packageName, packagePrefs);
}
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
// 若传入的文件名为空则设置为"null",仅对KITKAT以下版本应用有效
if (name == null) {
name = "null";
}
}
// 根据Prefs名称获取包路径下对应文件
sp = packagePrefs.get(name);
if (sp == null) {
// 创建SharedPreferences的存储文件
File prefsFile = getSharedPrefsFile(name);
// 用该文件创建SharedPreferences实例
sp = new SharedPreferencesImpl(prefsFile, mode);
// 放入缓存
packagePrefs.put(name, sp);
return sp;
}
}
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
// 若文件被其他进程修改,就重新加载该文件
// 指定MODE_MULTI_PROCESS标志位或HONEYCOMB以下版本有效
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
}
五、总结
总结流程如下:
- ApplicationThread 是 ActivityThread 的内部类,也是成员变量之一;
- ActivityThread 创建后把自己的 ApplicationThread 实例 IPC 注册到 ActivityManagerService;
- ActivityManagerService 登记 ApplicationThread 后 IPC 调用后者 bindApplication() 表示注册工作已完成,用 ApplicationThread 告知 ActivityThread 继续进行 Application 初始化;
- ApplicationThread.bindApplication() 通过发送标志为 BIND_APPLICATION 的 Message 告知 ActivityThread 进行 Applicatoin 初始化;
- ActivityThread 执行 handleBindApplication(),方法内部创建 Instrumentation 后保存在成员变量 mInstrumentation;
- 随后创建 ContextImpl 实例,把该实例保存在 mInstrumentation 实例中;
- 而 ContextImpl 获取 SharedPreferences 线程安全且 SharedPreferences 内部操作线程安全;
延伸问题,根据上面分析已知 SharedPreferences 线程安全。而 SharedPreferences 表面支持进程安全,即多个进程可同时写入文件。
但实际 Google 并不认可这种操作,因为多个进程同时写入文件的操作没法在系统层进行协调,不能保证其安全,所以可能会造成数据的丢失。
六、参考链接
- SharedPreferences and Thread Safety - StackOverflow
- SharedPreferences.Editor.apply()
- SharedPreferences.Editor.commit()