一、Android8.0以下SharedPreferences任务调度的实现
- SharedPreferencesImpl.apply()的实现:
public void apply() {
//创建一个result,它内部有一个计数器(初始值为1)
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);
}
每次进行apply操作时,创建了两个runnable:
- awaitCommit被添加到QueuedWork的finishers队列中:执行该runnable需要等待MemoryCommitResult中的计数器归零------即任务写入完成;
- postWriteRunnable
- 会被放入到一个单线程的线程池中执行:
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
- 执行结束后,会remove掉QueuedWork中被添加的awaitCommit;
- 会被放入到一个单线程的线程池中执行:
- MemoryCommitResult.writtenToDiskLatch何时归零:执行写入操作writeToFile()方法完成后;
- QueuedWork:SharedPreferences的任务标记执行队列
- finisher队列:sPendingWorkFinishers-------保存
awaitCommit
的队列 - 单线程池:sSingleThreadExecutor------执行写入操作
postWriteRunnable
的线程池 - 产生ANR的源头:waitToFinish()方法
- finisher队列:sPendingWorkFinishers-------保存
/**
* Finishes or waits for async operations to complete.
* (e.g. SharedPreferences$Editor#startCommit writes)
*
* 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() {
Runnable toFinish;
while ((toFinish = sPendingWorkFinishers.poll()) != null) {
toFinish.run();
}
}
该方法会循环的取出sPendingWorkFinishers中的runnable,并等待全部执行完成;
-
在ActivityThread消息队列的处理
以下是需要需要等待waitToFinish()方法执行完成,ActivityThread.H中的处理事件:
SERVICE_ARGS------handleServiceArgs()
STOP_SERVICE-------handleStopService()
PAUSE_ACTIVITY_FINISHING-------handlePauseActivity()
STOP_ACTIVITY_HIDE------handleStopActivity()
SLEEPING--------handleSleeping()
问题明了:
执行完apply()方法后,会产生一一对应的awaitCommit和postWriteRunnable,postWriteRunnable执行完成后会清除掉MemoryCommitResult中的计数器,并从删除QueuedWork.sPendingWorkFinishers中对应的awaitCommit。
即:当线程池sSingleThreadExecutor中写入任务未被全部完成,QueuedWork中sPendingWorkFinishers队列就不为空,QueuedWork.waitToFinish()方法就会依次执行sPendingWorkFinishers队队列中的任务------awaitCommit,awaitCommit在写入操作完成,会被writtenToDiskLatch阻塞。此时如果ActivityThread要处理以上事件,UI线程被waitToFinish()方法block,就有可能发生ANR。
解决思路:实现一个自定义的ConcurrentLinkedQueue,重写poll()方法强制返回null,然后动态代理掉QueuedWork中的sPendingWorkFinishers,waitToFinish()方法就不会产生阻塞。
二、Android8.0及以上上SharedPreferences任务调度的实现
我们先来看一下官方优化的改动点:
1. 将原来的单线程的线程池修改为HandlerThread;
2. QueuedWork中同时持有sWork工作队列和sFinishers锁队列,并将任务队列由ConcurrentLinkedQueue改为LinkedList实现;
3. 将写入文件的工作从原来直接抛到线程池中,改为直接移动到QueueWork中的sWork中。
SharedPreferencesImpl.apply()的实现与之前差异不大,核心在于QueuedWork的变化
新QueuedWork源码分析:
- 可延迟 Runnable 的延迟值。
/** Delay for delayed runnables, as big as possible but low enough to be barely perceivable */
private static final long DELAY = 100;
- 当 waitToFinish() 运行超过 MAX_WAIT_TIME_MILLIS 毫秒,发出警告
private static final long MAX_WAIT_TIME_MILLIS = 512;
- 本类使用的锁对象
private static final Object sLock = new Object();
- 执行任务时的锁对象
private static Object sProcessingWork = new Object();
- 两个队列:
//任务链表
private static final LinkedList sWork = new LinkedList<>();
//存放 Finisher 的链表
private static final LinkedList sFinishers = new LinkedList<>();
- 执行任务的handler
@GuardedBy("sLock")
private static Handler sHandler = null;
- 新任务是否能被延迟,默认为 true
@GuardedBy("sLock")
private static boolean sCanDelay = true;
- QueuedWorkHandler相关实现:
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();
}
}
}
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;
}
}
这是一个运行在HandlerThread上的handler;
- 任务入队:
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);
}
}
}
任务入队并执行,判断delay or not,执行processPendingWork();
由apply()触发的入队操作,shouldDelay都为true,即sCanDelay为true时,均延迟100ms执行;即默认情况下,processPendingWork()每100ms,触发一次打包执行操作--------将写入操作进行分片处理,每次只处理100ms内被添加进的任务;
- waitToFinish()方法:
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);
}
try {
while (true) {
Runnable finisher;
synchronized (sLock) {
finisher = sFinishers.poll();
}
if (finisher == null) {
break;
}
finisher.run();
}
} finally {
sCanDelay = true;
}
synchronized (sLock) {
long waitTime = System.currentTimeMillis() - startTime;
if (waitTime > 0 || hadMessages) {
mWaitTimes.add(Long.valueOf(waitTime).intValue());
mNumWaits++;
if (DEBUG || mNumWaits % 1024 == 0 || waitTime > MAX_WAIT_TIME_MILLIS) {
mWaitTimes.log(LOG_TAG, "waited: ");
}
}
}
}
- 清空handler中的消息,lsCanDelay = false;
- 执行processPendingWork();
- 循环执行sFinishers队列中的finisher,等在sFinishers中的锁集合的执行,即等待HandlerThread线程执行完写入操作;
由于processPendingWork()方法中的写入操作是加锁的(sProcessingWork),所以UI线程和HandlerThread触发的写入任务不会同时进行--------即当UI线程触发waitToFinish()时,需要等待HandlerThread线程执行写入操作完成,释放sProcessingWork锁,然后处理下一个100ms内等待执行的写入任务,而后续再被添加进的任务,UI线程不会再处理,而是继续交由HandlerThread线程执行;
waitToFinish()中任务执行前,会将sCanDelay置位false,processPendingWork()执行完成后,置为true。即当在UI线程触发waitToFinish()方法的过程中,如果此时再有写入工作,会向HandlerThread发送没有delay的写入操作消息,会在ui线程执行完成写入操作后,立即执行。
- processPendingWork()
private static void processPendingWork() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
synchronized (sProcessingWork) {
LinkedList work;
synchronized (sLock) {
work = (LinkedList) 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");
}
}
}
}
执行逻辑:
- clone工作队列sWork,并清空sWork;
- 清空QueueWork中的HandlerThread的消息队列中的MSG_RUN消息;
- 依次执行任务队列中的runnable;
procressPendingWork的执行入口有两个:
- handler在HandlerThread线程处理MSG_RUN消息;
- UI线程执行waitToFinish,
执行时会处理所有当前消息时间节点之前,sWorker队列中所有已存在的任务,所以需要清空队列中的MSG_RUN消息。
优化思路:对sWork进行动态代理,复写链表的clone和clear方法:当clone方法是UI线程调用时,返回一个空的集合,避免主线程执行写入文件的操作导致block。当clear方法被UI调用时,不做清空直接return。实际上将原来UI线程处理的写入任务,交给HandlerThread线程写入。