SharePreference
SharePreference是Android最基本的一个键值对存储,底层其实是使用的xml文件,通过app->内存->xml的二级缓存来实现效率的提高
我们以下四点来进行说明:
- SharePreference的基本使用
- put做了什么
- apply和commit的实现
- SharePreference的线程安全
- SharePreference的进程间通信
- mDiskWritesInFlight有什么作用
一、SharePreference的基本使用
写:
val sp = getSharedPreferences("default", MODE_PRIVATE)
sp.edit().putBoolean("isFirst",false).apply()//也可以用commit,后面会讲
读:
sp = getSharedPreferences("default", MODE_PRIVATE)
sp.getBoolean("isFirst",false)
put做了什么,源码位置:SharedPreferencesImpl.java
@GuardedBy("mEditorLock")
private final Map<String, Object> mModified = new HashMap<>();
@Override
public Editor putBoolean(String key, boolean value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
一个线程安全的往hashmap放key/value的方法
二、apply和commit的实现
2.1 先看看commit的实现
@Override
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
//.enqueueDiskWrite(mcr, null); 第二个参数为null,表示再当前县城
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
//这里是一个CountDownLatch,等上面写完,这里的阻塞会停止
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;
}
代码的流程如下:
这里面有概念我们先要进行一个大概的讲解:
- MemoryCommitResult类
- enqueueDiskWrite做了什么
2.1.1 MemoryCommitResult类
// Return value from EditorImpl#commitToMemory()
private static class MemoryCommitResult {
...
//需要写入磁盘的map
final Map<String, Object> mapToWriteToDisk;
//等待写文件的CountDownLatch
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
@GuardedBy("mWritingToDiskLock")
volatile boolean writeToDiskResult = false;
boolean wasWritten = false;
private MemoryCommitResult(long memoryStateGeneration, boolean keysCleared,
@Nullable List<String> keysModified,
@Nullable Set<OnSharedPreferenceChangeListener> listeners,
Map<String, Object> mapToWriteToDisk) {
....
this.listeners = listeners;
this.mapToWriteToDisk = mapToWriteToDisk;
}
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
//写文件完毕,唤醒await
writtenToDiskLatch.countDown();
}
}
通读这个类,发现就是一个用来把Map写入磁盘的工具类。然后来看一下commitToMemory做了什么
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
boolean keysCleared = false;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
//其他线程在使用
if (mDiskWritesInFlight > 0) {
//深拷贝map,因为有其线程正在读写,只能生成一个新的map
mMap = new HashMap<String, Object>(mMap);
}
mapToWriteToDisk = mMap;
//操作线程数++
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
//深拷贝Listener
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();
}
keysCleared = true;
mClear = false;
}
//深拷贝已经修改的值
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
if (v == this || v == null) {
if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
mapToWriteToDisk.remove(k);
} else {
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, keysCleared, keysModified,
listeners, mapToWriteToDisk);
}
这个方法,主要做两件事:
- 如果有其他线程再修改,先把当前的mMap产生一个新的
- 把modified和Listener全都深拷贝一份备用
2.1.2 enqueueDiskWrite做了什么
其实这里还有一个技术细节,那就是enqueueDisWrite的实现:
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//没有传runnable,isFromSyncCommit = true
final boolean isFromSyncCommit = (postWriteRunnable == null);
//写文件
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
//操作完线程数--
mDiskWritesInFlight--;
}
//这里postWriteRunnable == null
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
//同步commit执行
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
//有其他线程执行
wasEmpty = mDiskWritesInFlight == 1;
}
//直接调用写文件
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
//放入队列执行
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
总结一下:
- isFromSyncCommit是否同步提交,mDiskWritesInFlight表示有几个线程在执行,如果只有一个线程(自己)再执行直接调用writeToDiskRunnable.run();
- 如果有多个线程执行,那就放入队列,等写完调用countdown,继续执行
- QueueWork是一个处理队列的工具类
2.1.3 总结一下commit
先深拷贝一份需要的内存变量,放到MemoryCommitResult,如果只有当前自己这一个线程,直接写入到文件中。如果有多个线程再执行,那就放入队列中
2.2 apply的源码
接下来我们看看apply的不同
@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();
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//放入写磁盘的队列
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
apply就是两个runnable。一个是等待文件写完的,一个是写文件的。依据我们前面分析的enqueueDiskWrite,这里不会走直接提交
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//没有传runnable,isFromSyncCommit = true
final boolean isFromSyncCommit = (postWriteRunnable == null);
...
//同步commit执行
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
//有其他线程执行
wasEmpty = mDiskWritesInFlight == 1;
}
//直接调用写文件
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
//放入队列执行
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
因为postWriteRunnable!=null,所以isFromSyncCommit = true,会直接走到 QueuedWork.queue,enquque的第二个参数是表示是否delay,默认值为100ms
2.2.1 apply小总结
apply会延迟100毫秒,放入延迟100ms放入写文件的队列中。在写文件完成后,结束countdownLatch。
三、SharePreference的线程安全
3.1 存储的线程安全
@Override
public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor putStringSet(String key, @Nullable Set<String> values) {
synchronized (mEditorLock) {
mModified.put(key,
(values == null) ? null : new HashSet<String>(values));
return this;
}
}
@Override
public Editor putInt(String key, int value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor putLong(String key, long value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor putFloat(String key, float value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor putBoolean(String key, boolean value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor remove(String key) {
synchronized (mEditorLock) {
mModified.put(key, this);
return this;
}
}
@Override
public Editor clear() {
synchronized (mEditorLock) {
mClear = true;
return this;
}
}
3.2 保存的线程安全
前面我们说了:
- commit无多线程情况下,会直接在调用线程保存,若有多线程,则在QueueWork,使用MesssageQueue与Handler执行
- apply也是用MesssageQueue与Handler执行,不过每次会delay 100ms
四、进程间通信
SP的基本无进程间通信,所谓的进程间通信,也即是每次会reload一次xml。源码如下:
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl 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;
}
这样一来,要保证数据同步,每次都必须要调用这个方法,不能存储SharePreference对象。sp的缓存机制将失效,所以不推荐使用MODE_MULTI_PROCESS。本身是有很多设计缺陷的
最后的总结:
- commit单线程情况下,会直接在当前线程写入文件,如果是多线程同时操作的情况下(mDiskWritesInFlight>1),会放入QueueWork的队列中执行
- apply总是会放到QueueWork的队列中,延迟100ms执行
- sp的线程安全包括修改安全和保存安全,第一个是用Sychronized关键字,第二个是用MessageQueue来保证线程安全的。
- sp支持多进程的操作值MULTI_PROCESS,但其实质是每次load xml,并无做特殊操作,