一.简介
SharedPreferences 是 Android 中一种轻量级的数据存储方式,数据以键值对,文件以 xml 的形式存储在 /data/data/
在源码中 SharedPreferences 是一个接口,具体的实现是
SharedPreferencesImpl1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/**
* Interface for accessing and modifying preference data returned by {@link
* Context#getSharedPreferences}. For any particular set of preferences,
* there is a single instance of this class that all clients share.
* Modifications to the preferences must go through an {@link Editor} object
* to ensure the preference values remain in a consistent state and control
* when they are committed to storage. Objects that are returned from the
* various <code>get</code> methods must be treated as immutable by the application.
*
* <p><em>Note: This class does not support use across multiple processes.</em>
*
* ...
*/
public interface SharedPreferences {
...
}
final class SharedPreferencesImpl implements SharedPreferences {
...
}
在注释中对 SharedPreferences 以下称(SP)解释如下:
- 对于任何特定的 SP ,所有客户端共享此类的单个实例(也就是应该使用单例模式)。对 SP 数据的修改必须通过一个 SharedPreferences.Editor 对象来确保 SP 数据保持一致状态,并在它们提交存储时进行控制。从各种get 方法返回的对象必须被应用程序视为不可变的。
注意:此类提供强大的一致性保证。它使用昂贵的操作可能会减慢应用程序的速度。经常改变可以容忍损失的属性或属性应该使用其他机制。有关详细信息读取上的评论 SharedPreferences.Editor.commit() 和SharedPreferences.Editor.apply()。
(换句话说就是 commit 和 apply 用于对数据进行保存,为了保证一致性这个过程可能会减慢应用程序的速度,如果对一致性要求不高则可以使用其他数据存储机制。)
==注意:此类不支持跨多个进程使用。==
二.获取 Sp
获取一个 Sp 有三种方式
- 在一个 Activity 中调用 getPreferences(int mode)
- 使用 PreferenceManager.getDefaultSharedPreferences(Context context)
- context.getSharedPreferences(String name, int mode)
第一种方式和第二种方式最后都会使用 第三种方式,不同的使用的名字不同,模式现在都是为 MODE_PRIVATE 其他的都已经废弃不使用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//第一种
//Activity
//
public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
return getSharedPreferences(getLocalClassName(), mode);
}
//第二种
//PreferenceManager
public static SharedPreferences getDefaultSharedPreferences(Context context) {
return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
getDefaultSharedPreferencesMode());
}
/**
* Returns the name used for storing default shared preferences.
*
* @see #getDefaultSharedPreferences(Context)
* @see Context#getSharedPreferencesPath(String)
*/
public static String getDefaultSharedPreferencesName(Context context) {
return context.getPackageName() + "_preferences";
}
private static int getDefaultSharedPreferencesMode() {
return Context.MODE_PRIVATE;
}
下面到 context 的具体实现类 contextImpl 中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@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.
// 允许名字为 null, 即文件的名字为 null.xml
if (mLoadedApk.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {
//mSharedPrefsPaths 的数据结构为 ArrayMap<String, File>
//用于保存名字 和对应的文件
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
//如果没有这个文件就创建这个文件
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
//使用 synchronized 进行线程安全的保证
synchronized (ContextImpl.class) {
//一个 SP xml 文件对应 一个 SharedPreferencesImpl 实例
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
checkMode(mode);
...
//创建 SharedPreferencesImpl
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
....
return sp;
}
// 获取用于缓存 SharedPreferencesImpl 的 packagePrefs, 一个包名对应一个
// packagePrefs
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);
}
return packagePrefs;
}
下面就到 SharedPreferencesImpl.java 中 查看创建一个 SharedPreferencesImpl 的过程。
1 |
|
获取 SharedPreferences 总结:
- 获取 SP 的过程是通过 synchronized 关键字保证多线程安全的。
- 通过 Map 进行缓存 Sp 实例,因此多次调用 getSharedPreferences 几乎没有性能上的差别。
- 获取 Sp 的时候就会通过一个线程将 xml 数据从磁盘加载到内存中。这个过程会加锁,加载完成后会设置 mLoaded 标志,并唤醒其他线程。
三.get 方法
Sp 支持的数据类型为 int , long , float, boolean ,String 和 Set.
下面以 getString 为例1
2
3
4
5
6
7
8
9
10
11
12//SharedPreferencesImpl.java
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
//进行线程安全保证
synchronized (mLock) {
//等待加载完成后才能读
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
1 | @GuardedBy("mLock") |
getXXX 方法 总结:
- 通过 synchronized 进行线程安全保证
- 在主线程进行获取,但是需要等加载的完成后才能进行读,所以get 方法可能造成主线程阻塞,从而导致 ANR 。
- 加载完成后读的过程只涉及内存的读。
四.putXXX 和 apply/commit
提交数据的时候首先要获取 Editor 对象1
2
3
4
5
6
7
8
9
10@Override
public Editor edit() {
//这里也是需要等待加载 xml 文件到内存完成后
//才能创建 EditorImpl
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
以 putString 为例1
2
3
4
5
6
7
8
9@Override
public Editor putString(String key, @Nullable String value) {
//使用 synchronized 进行线程安全保证
synchronized (mEditorLock) {
//将数据暂时保存到 mModified 这个 Map 中
mModified.put(key, value);
return this;
}
}
1.apply
1 | @Override |
1 | private MemoryCommitResult commitToMemory() { |
回到 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
39
40
41
42
43
44
45
46
47
48@Override
public void apply() {
final long startTime = System.currentTimeMillis();
//将修改先写入内存
final MemoryCommitResult mcr = commitToMemory();
//这里只是创建了一个 Runnable ,并不是一个线程
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
//注意这里会进行等待也就是 需要 MemoryCommitResult 的 setDiskWriteResult 方法执行后
//才能返回
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
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
//这里执行了上面的 awaitCommit 的 run 方法
//不是 start
//并将队列中的 awaitCommit 移除
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//添加到队列中
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);
}
1 | private void enqueueDiskWrite(final MemoryCommitResult mcr, |
在 QueuedWork.java 中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
30public static void queue(Runnable work, boolean shouldDelay) {
//getHandler 获取的是一个 handlerThread 的hanlder ,也就是一个子线程
Handler handler = getHandler();
synchronized (sLock) {
sWork.add(work);
if (shouldDelay && sCanDelay) {
//发送一个消息 MSG_RUN 到 handler 所在线程,也就是 handlerThread 子线程中去
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
//创建一个 handlerThread ,并执行 start 方法
//这就是 apply 写到磁盘的线程
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
1 | // Handler 的处理 |
QueuedWork.addFinisher(awaitCommit);
在上面的方法中注意到 对每个 apply 都会创建一个相应的 awaitCommit,并添加到 QueuedWork 的一个队列中,但是在 QueuedWork 注意到有这样一个方法 waitToFinish1
2
3
4
5
6
7
8
9
10
/**
* 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() {}
这个方法会在 Activity 暂停时或者 BroadcastReceiver 的 onReceive 方法调用后或者 service 的命令处理后被调用,并且调用这个方法的目的是为了确保异步任务被及时完成。
可以看到 waitToFinish 都是在 ActivityThread 中,也就是主线程调用的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
47public static void waitToFinish() {
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);
}
// We should not delay any work as this might delay the finishers
sCanDelay = false;
}
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
//这个方法就会执行 所有的 Runnable 的run 返回
//这个时候 processPendingWork 是执行在主线程中
processPendingWork();
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
try {
while (true) {
Runnable finisher;
synchronized (sLock) {
finisher = sFinishers.poll();
}
if (finisher == null) {
break;
}
finisher.run();
}
} finally {
sCanDelay = true;
}
...
}
这种设计是为了保证 SP 可靠的、保证写入完成的存储机制。
apply 总结
- apply 没有返回值
- apply 是在主线程将修改数据提交到内存, 然后再子线程(HandleThread)提交到磁盘
- apply 会将 Runnble 添加到 QueueWork 中,如果主线程 Activity 暂停时或者 BroadcastReceiver 的 onReceive 方法调用后或者 service 的命令处理,就会在主线程执行 提交到硬盘的方法,这个过程就会造成主线程 ANR
2.commit
1 | public boolean commit() { |
1 | private void enqueueDiskWrite(final MemoryCommitResult mcr, |
commit 总结
- commit 有返回值
- commit 是在主线程将修改数据提交到内存, 然后再在主线程提交到磁盘
- 用 commit 方法最保险。如果担心在主线程调用 commit 方法会出现 ANR,可以将所有的 commit 任务放到单线程池的线程里去执行。
总结
- Sp 主线程 getXX 方法会 ANR
- Sp apply 方法会 ANR
- Sp 主线程调用 commit 方法 ANR