0%

SharedPreferences源码分析

起因

在项目开发中发现很多数据都是用SharedPreferences做本地保存的,操作SharedPreferences只需要建立Editor,然后这个向这个Editor对象put各种各样的键值对,最后调用它的commit或者apply保存信息即可,非常简单方便。同时注释文档中说明commit是同步的而apply是异步的,说明SharedPreferences是支持多线程的,那就有些疑惑了:

  • 假设要保存的数据很多,apply异步线程保存,同时主线程再去读取数据,读取数据会不会等待?
  • 在异步保存时退出Activity,退出应用有影响吗? 下面将带着这两个问题去学习SharedPreferences的实现

分析

1.获取SP对象

调用Activity的getSharedPreferences方法,发现这是父类ContextWrapper的方法,如下:

1
2
3
4
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}

这个方法只是再次调用mBase的相应方法,mBase是ContextImpl对象,而每一个ContextWrapper都会被绑定ContextImpl,ContextWarpper子类有Application、Service、Activity,所以在这三个常见的Context中都可以使用SP。

接着看看ContextImpl中的getSharedPreferences的实现:

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
@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);//创建File对象,name + ".xml"
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}

上面的mSharedPrefsPaths是一个Map对象,它保存的是SP的名字与文件的对应关系,如果没有对应的File就创建一个File对象,注意mSharedPrefsPaths是对象的成员,但是在操作这个对象时用的不是对象锁而是类锁,为什么?接着看最后调用的它的重载方法,如下:

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

/**
* Map from package name, to preference name, to cached preferences.
静态的sSharedPrefsCache
*/
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;

@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
//file -> SP 的map
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
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);
cache.put(file, sp);
return sp;
}
}

//获取静态的sSharedPrefsCache
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}

final String packageName = getPackageName();
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}



在这里创建、返回了正儿八经干活的SharedPreferencesImpl对象,以上的分析可以总结为:获取SP时存在这样的映射关系: (对象中sp name) -> (对象 file) -> (全局的 sp)的关系,这个映射关系为了确保万无一失在操作的过程中全部使用类锁,究竟在什么情况会出错,我的单线程脑子还没想出来。。 。

2. 操作SP对象

2.1 新建与get方法

首先要明确这个SP对象它是static的,任意线程只要姿势对了都可以通过Context操作它,所以对它的操作全部都加了对象锁,构造方法如下:

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
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
//加载数据
startLoadFromDisk();
}

private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}

new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
//开启新的线程加载数据
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}

// Debugging
if (mFile.exists() && !mFile.canRead()) {
Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
}

Map<String, Object> map = null;
StructStat stat = null;
Throwable thrown = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
//将XML文件转化为内存中的键值对
map = (Map<String, Object>) XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
// An errno exception means the stat failed. Treat as empty/non-existing by
// ignoring.
} catch (Throwable t) {//比较豪放,任意问题都catch住
thrown = t;
}

synchronized (mLock) {
mLoaded = true;
mThrowable = thrown;

// It's important that we always signal waiters, even if we'll make
// them fail with an exception. The try-finally is pretty wide, but
// better safe than sorry.
try {
if (thrown == null) {
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
}
// In case of a thrown exception, we retain the old map. That allows
// any open editors to commit and store updates.
} catch (Throwable t) {
mThrowable = t;
} finally {
mLock.notifyAll();//通知其他线程加载完成了
}
}
}

上面的代码中将所有保存在本地的数据都读取到了mMap中,接下来的getXXX方法都是从这个mMap中获取值

1
2
3
4
5
6
7
8
public Map<String, ?> getAll() {
synchronized (mLock) {
awaitLoadedLocked();//等待锁释放
//noinspection unchecked
return new HashMap<String, Object>(mMap);
}
}

2.2 edit方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
// Editor, but then we should be able to do:
//
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
synchronized (mLock) {
awaitLoadedLocked();
}

return new EditorImpl();
}

直接返回了EditorImpl对象,它是SharedPreferences.Editor的实现类。

1
2
3
4
5
6
7
8
9
10
//保存更改的键值对
private final Map<String, Object> mModified = new HashMap<>();
//
@Override
public Editor putBoolean(String key, boolean value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}

调用putXXX方法时数据被保存在了mModified键值对中,在commit或者apply的时候提交,下面先看commit方法:

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
      @Override
public boolean commit() {
long startTime = 0;

if (DEBUG) {
startTime = System.currentTimeMillis();
}
//将mModified的变化同步给mMap
MemoryCommitResult mcr = commitToMemory();

SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " committed after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}

private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;

synchronized (SharedPreferencesImpl.this.mLock) {
// We optimistically don't make a deep copy until
// a memory commit comes in when we're already
// writing to disk.
if (mDiskWritesInFlight > 0) {//此刻有其他数据正在提交
// We can't modify our mMap as a currently
// in-flight write owns it. Clone it before
// modifying it.
// noinspection unchecked
mMap = new HashMap<String, Object>(mMap);
}
mapToWriteToDisk = mMap;
mDiskWritesInFlight++;//正在同步+1

boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}

synchronized (mEditorLock) {
boolean changesMade = false;

if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}

for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// "this" is the magic value for a removal mutation. In addition,
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
if (v == this || v == null) {//为null删除mMap的数据
if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
mapToWriteToDisk.remove(k);
} else {//更新mMap中的数据
if (mapToWriteToDisk.containsKey(k)) {
Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mapToWriteToDisk.put(k, v);
}

changesMade = true;
if (hasListeners) {
keysModified.add(k);
}
}

mModified.clear();

if (changesMade) {
mCurrentMemoryStateGeneration++;
}

memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}

上面将mModified的键值同步给了mMap,并且返回了MemoryCommitResult对象,顾名思义它是同步的结果,它的构造方法的最后一个参数其实就是mMap对象,此刻mMap中已经同步了mModified的值,接下来就顺理成章地要将mMap转换成xml文件了,看commit()中的调用的SharedPreferencesImpl.this.enqueueDiskWrite方法:

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
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//是否是同步commit,commit方法中postWriteRunnable为null
final boolean isFromSyncCommit = (postWriteRunnable == null);
//写文件的runnable
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};

// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {//此刻只有这一个commit
wasEmpty = mDiskWritesInFlight == 1;
}

if (wasEmpty) {//只有一个提交,就在主线程写文件了
writeToDiskRunnable.run();
return;
}
}

QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

接下来看apply方法:

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
public void apply() {
final long startTime = System.currentTimeMillis();
//和commit一样,也调用了commitToMemory同步mModified
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}

if (DEBUG && mcr.wasWritten) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " applied after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
};

QueuedWork.addFinisher(awaitCommit);

Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//也调用了enqueueDiskWrite将数据写到文件中
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

// Okay to notify the listeners before it's hit disk
// because the listeners should always get the same
// SharedPreferences instance back, which has the
// changes reflected in memory.
notifyListeners(mcr);
}

apply的调用逻辑和commit是一样的都是先将mModified的数据同步给mMap。
到这里开头的第一个问题就有了答案:
不管是commit还是apply,最后都是将Editor的修改的数据同步赋值给SP的mMap对象之后才会做写文件的操作,所以apply时立即读数据,读的是内存mMap的数据,不会等待写完了然后再读文件,要等待也只是等待同步的过程。

apply调用enqueueDiskWrite写文件时postWriteRunnable不为null,直接跳到 enqueueDiskWrite方法的QueuedWork.queue处,将任务进队列了,而QueuedWork是什么?结合apply的异步写功能,很容易想到它是一个排队执行的异步线程,QueuedWork如下:

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
public class QueuedWork {

private static final Object sLock = new Object();

private static Object sProcessingWork = new Object();

private static Handler sHandler = null;

private static final LinkedList<Runnable> sWork = new LinkedList<>();

//新建HandlerThread 线程以及一个对应的QueuedWorkHandler
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
//
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}



//进队列方法,将work保存到sWork中,并通知使用handler去通知handlerThread执行
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();

synchronized (sLock) {
sWork.add(work);

if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}

/**
* @return True iff there is any {@link #queue async work queued}.
*/
public static boolean hasPendingWork() {
synchronized (sLock) {
return !sWork.isEmpty();
}
}
//取出sWork中的runnable挨个执行
private static void processPendingWork() {
long startTime = 0;

if (DEBUG) {
startTime = System.currentTimeMillis();
}

synchronized (sProcessingWork) {
LinkedList<Runnable> work;

synchronized (sLock) {
work = (LinkedList<Runnable>) sWork.clone();
sWork.clear();

// Remove all msg-s as all work will be processed now
getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
}

if (work.size() > 0) {
for (Runnable w : work) {
w.run();
}

if (DEBUG) {
Log.d(LOG_TAG, "processing " + work.size() + " items took " +
+(System.currentTimeMillis() - startTime) + " ms");
}
}
}
}


/**
* Trigger queued work to be processed immediately. The queued work is processed on a separate
* thread asynchronous. While doing that run and process all finishers on this thread. The
* finishers can be implemented in a way to check weather the queued work is finished.
*
* Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive,
* after Service command handling, etc. (so async work is never lost)
*/
public static void waitToFinish() {
long startTime = System.currentTimeMillis();
boolean hadMessages = false;

Handler handler = getHandler();

synchronized (sLock) {
if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
// Delayed work will be processed at processPendingWork() below
handler.removeMessages(QueuedWorkHandler.MSG_RUN);

if (DEBUG) {
hadMessages = true;
Log.d(LOG_TAG, "waiting");
}
}

// We should not delay any work as this might delay the finishers
sCanDelay = false;
}

StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
processPendingWork();
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}

}


private static class QueuedWorkHandler extends Handler {
static final int MSG_RUN = 1;

QueuedWorkHandler(Looper looper) {
super(looper);
}

public void handleMessage(Message msg) {
if (msg.what == MSG_RUN) {
processPendingWork();
}
}
}
}

QueuedWork封装了HandlerThread,queue方法将runnable保存到sWork中,并通知使用handler去通知handlerThread取sWork中的runnable执行,最后在子线程调用processPendingWork取出sWork中的runnable挨个执行,这时退出应用会怎么样?
QueuedWork有一个waitToFinish方法,看这个方法名大致知道它是退出前的被调用的方法,它被调用地方如下图:
调用waitToFinish的地方

一目了然,在Activity、Service stop的时候会调用这个方法。再看看这个方法的实现,等待HandlerThread中的上一个runnable结束后,判断这个线程的Looper的队列中是否有消息,如果有就接管这个事件,取sWork中所有的Runnable在主线程执行。

所以,退出Activity、退出应用不会导致apply的数据丢失,它会在退出时将异步线程切换到主线程来执行,等待数据都保存了才会退出。

参考 : SharedPreferences的使用及源码浅析