SharedPreferences 代码分析
ContextImpl.getSharedPreferences
ContextImpl.getSharedPreferences(String name, int mode)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public ContextImpl.SharedPreferences getSharedPreferences(String name, int mode) {
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);
}
}
// 上面代码就是通过 name 获取到对应的 file
return getSharedPreferences(file, mode);
}
ContextImpl.getSharedPreferences(File file, int mode)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache =getSharedPreferencesCacheLocked();
// 分析getSharedPreferencesCacheLocked 可知所有 Context 获取到的是同一个 cache 对象
// File 对象如果路径相同那么File.equals 和 File.hashCode 也相同,所以可知在所有 Context 中 cache.get(file);获取到的 SharedPreferences 都是同一个对象
sp = cache.get(file);
if (sp == null) {
checkMode(mode);
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
// 如果是多进程模式,每次获取 SharedPreferences 都会重新从磁盘中获取数据
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < android.os.BuildVERSION_CODES.HONEYCOMB) {
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
ContextImpl.getSharedPreferencesCacheLocked
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//ContextImpl.sSharedPrefsCache 是一个静态变量,因此所有Context 都是用的同一个 sSharedPrefsCache对象
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
@GuardedBy("ContextImpl.class")
private ArrayMap<File, SharedPreferencesImpl>getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
final String packageName = getPackageName();
// 因为 sSharedPrefsCache 在所有 Context 中都是同一个对象,所以 packagePrefs 对象也是同一个
ArrayMap<File, SharedPreferencesImpl> packagePrefs =sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
结论: 从上面分析可知 所有 Context.getSharedPreferences 如果 name 相同那么他们获取的是同一个对象
SharedPreferencesImpl
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
private Map<String, Object> mMap; //这个对象是存放 SharedPreferences 的 Key Value 值
@UnsupportedAppUsage
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();// 从硬盘中读取 Key Value 并存放到 mMap 中
}
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") { // 这是虽然开启了线程但是在诸如 getFloat 等方法中会通过 wait 的形式等待 加载完毕
public void run() {
loadFromDisk();// 在子线程中从硬盘中读取 Key Value 并存放到 mMap 中
}
}.start();
}
public float getFloat(String key, float defValue) {
synchronized (mLock) {
awaitLoadedLocked(); // 等待 loadFromDisk()完毕
Float v = (Float)mMap.get(key);// 从内存中获取对应的值
return v != null ? v : defValue;
}
}
总结:从上面代码中得知 SharedPreferencesImpl 对象在构造方法中从硬盘获取到Map值,之后只要不主动调用Context.reloadSharedPreferences() 就不会再从硬盘中获取
EditorImpl 分析存入的过程
分析代码需要先看懂 CountDownLatch 的作用
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
96
97
98
99
100
101
102
103
104
105
//用来暂存通过 Editor 修改的内容
private final Map<String, Object> mModified = new HashMap<>();
// putString 只是将修改的内容暂存到 mModified 并没有将修改保存到 SharedPreferencesImpl.mMap 中,更没有保存到硬盘中
public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
@Override
public void apply() {
final long startTime = System.currentTimeMillis()
// 将修改提交到内存中
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
mcr.writtenToDiskLatch.await();//writtenToDiskLatch 是 CountDownLatch 类,当写入磁盘操作执行完成将 writtenToDiskLatch 置为0 ,才会继续向下执行。总的来说,就是等待写入磁盘操作执行完成
} catch (InterruptedException ignored) {
}
}
QueuedWork.addFinisher(awaitCommit)
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
}
// // 将修改提交到硬盘中,因为第二个参数不为 null 所以这是个异步任务
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable)
notifyListeners(mcr);
}
@Override
public boolean commit() {
// 将修改提交到内存中
MemoryCommitResult mcr = commitToMemory()
// 将修改提交到硬盘中,因为第二个参数为 null 所以这是个同步任务
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null);
try {
mcr.writtenToDiskLatch.await();// 等待写入磁盘操作执行完成
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
//
private void SharedPreferencesImpl.enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
// 如果 postWriteRunnable 为 null 代表是一个同步任务
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// 如果是同步任务则直接执行,commit
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
// 同步任务执行完成直接 return
return;
}
}
// 异步任务执行方法,apply
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
//添加到 sWork 中
sWork.add(work);
// sWork 内 work ,会有两种方式触发执行
// 1. 在 Activity 执行 onStop onPause 后调用 QueuedWork.waitToFinish 的方法内执行 , 这时候是在主线程执行的
// 2. Handler 接收到 QueuedWorkHandler.MSG_RUN 消息时执行,因为这个 Handler 是子线程 Handler,所以是在子线程执行的
if (shouldDelay && sCanDelay) {
// DELAY 默认值为 100 ,会在 100 毫秒后执行 sWork 内的 work ,handler是子线程 hander 所以如果是 handler执行是在子线程执行 work
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
总结:commit 方法是会直接将修改提交到内存和硬盘中的,apply 方法是先将修改提交到内存中,而提交到硬盘中的操作会在 Activity 执行 onStop onPause 或者 通过Hanlder 100毫秒后执行
分析为什么 SharedPreferences 不能跨进程
由于同一个 name 的 SharedPreferences 只存在一个对象,并且 SharedPreferences 读取硬盘的操作,在正常流程中只会在执行构造方法的地方执行一次
子进程修改 SharedPreferences 内存中的值,因为进程间内存隔离的原因是不会反映到主进程中的, 修改硬盘中的值,如果主进程对应的 SharedPreferences 还没有创建,这次修改是可以反应到主进程中的,但是仅限这次修改;如果主进程 SharedPreferences 已经构造完成,就不会从硬盘中获取对应的数据,子进程修改的值也就不会反应到子进程中。
即使将模式改为 MODE_MULTI_PROCESS ,每次调用 Context.getSharedPreferences 都会检查磁盘文件是否修改,如果已经修改重新从磁盘读取数据,这个方式不仅会增加获取 getSharedPreferences 的时间消耗,也会因为多进程并发修改文件而产生安全问题。推荐使用 ContentProvider 、跨进程通讯封装、MMKV
所以总的来说 SharedPreferences 是不能跨进程的