此篇主要讲commit和apply操作的实现
我们对这玩意儿的印象是commit是同步操作,apply是异步操作,尽量用apply,少用commit。
那么问题来了,
- 线上卡顿日志里大量的SharedPreferences引起的卡顿是怎么来的
- 先apply之后,立即commit,会发生什么
带着这些问题,我们开始看源码
EditorImpl.java
public void apply() {
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.add(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
QueuedWork.remove(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);
}
流程上看是先修改内存数据,在QueuedWork里添加等待锁,然后再排队修改文件
private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {
// 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);
}
mcr.mapToWriteToDisk = mMap;
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
mcr.keysModified = new ArrayList<String>();
mcr.listeners =
new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (this) {
if (mClear) {
if (!mMap.isEmpty()) {
mcr.changesMade = true;
mMap.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) {
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
} else {
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mMap.put(k, v);
}
mcr.changesMade = true;
if (hasListeners) {
mcr.keysModified.add(k);
}
}
mModified.clear();
}
}
return mcr;
}
首先,如果目前等待写入文件的操作个数不为0(mDiskWritesInFlight > 0),则基于当前内存数据,复制一份,进行修改,如果之前的修改都已写入文件,则直接修改原内存数据,待写入文件个数mDiskWritesInFlight加1。
根据mModified里的修改和原内存数据mMap比较,设置changesMade的值,标记是否有改动。
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr);
}
synchronized (SharedPreferencesImpl.this) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
final boolean isFromSyncCommit = (postWriteRunnable == null);
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (SharedPreferencesImpl.this) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}
这里是apply写文件的时机,在QueuedWork的单线程里排队执行,文件写入完成之后,待写入的个数减1,删除QueuedWork里的当前写操作的等待锁。
再来看commit:
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
commit修改内存方法与apply一致,排队写操作与apply共用enqueueDiskWrite方法,传入参数不同,此时不需要在QueuedWork里添加写操作等待锁,直接在调用线程等待结果。
回到enqueueDiskWrite方法:
final boolean isFromSyncCommit = (postWriteRunnable == null);
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (SharedPreferencesImpl.this) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
此时,如果前面待写入文件的操作个数为1,即只有当前commit的修改,则在当前线程执行写文件,否则和apply操作一样,进入单线程排队执行。
源码分析结束,回答开始的问题,
- Service stop和Activity stop 生命周期执行的时候会调用QueuedWork.waitToFinish(),此时会在主线程调用所有SharedPreferences的写操作等待锁,阻塞主线程直到写操作全部结束,如果此时还存在大量的apply,则大概率会卡顿。
- 调用apply之后立即commit,如果前一个apply还没写入文件,则commit会排队等待apply写入,否则直接写文件。
网友评论