异步执行 Message
Handler 发送的 Message 都是同步的,意味着大家都按照 when 的先后进行排序,谁先到谁执行。
如果遇到优先级高的 Message 可以通过 FrontQueue 发送插队 Message即可。但如果是希望同步的队列停滞只执行指定 Message 的话,即 Message 异步执行,现有的 API 是不够的。
事实上 Android 提供了同步屏障的机制来实现这一需求,不过主要面向的是系统 App 或 系统,App 可以通过反射来使用。
通过异步 Handler 实现
除了一般使用的 Handler 构造函数以外,Handler 还提供了创建发送异步 Message 的专用构造函数。通过该 Handler 发送的 Message 或 Runnable 都是异步的。我们将其称为异步 Handler
。
// Handler.java @UnsupportedAppUsage public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; } @NonNull public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) { if (looper == null) throw new NullPointerException("looper must not be null"); if (callback == null) throw new NullPointerException("callback must not be null"); return new Handler(looper, callback, true); }
我们启动一个 HandlerThread
来测试一下同步屏障的使用:分别构建一个普通 Handler
和 异步 Handler
。
private fun startBarrierThread() { val handlerThread = HandlerThread("Test barrier thread") handlerThread.start() normalHandler = Handler(handlerThread.looper) { msg -> Log.d(...) true } barrierHandler = Handler.createAsync(handlerThread.looper) { msg -> Log.d(...) true } }
启动 HandlerThread 并向两个 Handler 各发送一个 Message。
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... startBarrierThread() testNormalMessage() testSyncBarrierByHandler() } private fun testNormalMessage() { sendMessageRightNow(normalHandler, 1) } private fun testSyncBarrierByHandler() { sendMessageRightNow(barrierHandler, 2) }
是异步 Handler 的 Message 先执行吗?非也,因为我们还没有通知 MessageQueue 建立同步屏障!
09-24 23:02:19.032 28113 28113 D MainActivity: onCreate() 09-24 23:02:19.150 28113 28141 D MainActivity: Normal handler message occurred & what:1 09-24 23:02:19.150 28113 28141 D MainActivity: Barrier handler message occurred & what:2
除了发送异步Handler 发送异步 Message 以外,需要通过反射
事先建立起同步屏障。
注意:建立同步屏障必须早于需要屏蔽的同步 Message,否则无效,后面的原理会提及。
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... startBarrierThread() // 建立一个同步屏障 postSyncBarrier(barrierHandler.looper) testNormalMessage() testSyncBarrierByHandler() } private fun postSyncBarrier(looper: Looper) { Log.d(...) val method: Method = MessageQueue::class.java.getDeclaredMethod("postSyncBarrier") barrierToken = method.invoke(looper.queue) as Int }
这样子便可以看到,异步 Message 执行了,而且同步 Message 永远得不到执行。
09-24 23:11:36.176 28600 28600 D MainActivity: onCreate() 09-24 23:11:36.296 28600 28600 D MainActivity: Add sync barrier 09-24 23:11:36.300 28600 28629 D MainActivity: Barrier handler message occurred & what:2
原因在于建立的同步屏障尚未移除,永远只处理队列里的异步 Message。想要让同步 Message 恢复执行的话 remove 同步屏障即可,同样也需要反射!
我们在异步 Handler 执行结束后移除同步屏障。
private fun startBarrierThread() { ... barrierHandler = Handler.createAsync(handlerThread.looper) { msg -> Log.d(...) // 移除同步屏障 removeSyncBarrier(barrierHandler.looper) true } } fun removeSyncBarrier(looper: Looper) { Log.d(...) val method = MessageQueue::class.java .getDeclaredMethod("removeSyncBarrier", Int::class.javaPrimitiveType) method.invoke(looper.queue, barrierToken) }
可以看到同步 Message 恢复了。
09-24 23:10:31.533 28539 28539 D MainActivity: onCreate() 09-24 23:10:31.652 28539 28568 D MainActivity: Barrier handler message occurred & what:2 09-24 23:10:31.652 28539 28568 D MainActivity: Remove sync barrier 09-24 23:10:31.653 28539 28568 D MainActivity: Normal handler message occurred & what:1
通过异步 Message 实现
没有专用的异步 Handler 的时候,可以向普通 Handler 发送一个 isAsync
属性为 true 的 message,效果和异步 Handler 是一样的。当然这种方式仍旧需要建立同步屏障。
在原有的发送 Message 的函数里加入 isAsync 的重载参数。
private fun sendMessageRightNow(handler: Handler, what: Int, isAsync: Boolean = false) { Message.obtain().let { it.what = what it.isAsynchronous = isAsync handler.sendMessage(it) } }
向普通 Handler 发送异步 Message。
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... testNormalMessage() // 改用 Message 方式发送异步 Message testSyncBarrierByMessage() } private fun testSyncBarrierByMessage() { sendMessageRightNow(normalHandler, 2, true) }
同样记得在异步 Message 收到后移除同步屏障。
private fun startBarrierThread() { ... normalHandler = Handler(handlerThread.looper) { msg -> Log.d(...) if (2 == msg.what) removeSyncBarrier(barrierHandler.looper) true } }
结果和异步 Handler 的方式一致。
09-24 23:58:05.801 29040 29040 D MainActivity: onCreate() 09-24 23:58:05.923 29040 29040 D MainActivity: Add sync barrier 09-24 23:58:05.923 29040 29070 D MainActivity: Normal handler message occurred & what:2 09-24 23:58:05.924 29040 29070 D MainActivity: Remove sync barrier 09-24 23:58:05.924 29040 29070 D MainActivity: Normal handler message occurred & what:1
原理
先来看一下同步屏障是怎么建立的。
// MessageQueue.java // 默认是调用的时刻开始建立屏障 public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); } // 同步屏障支持指定开始的时刻 // 默认是调用的时刻,而 0 表示? private int postSyncBarrier(long when) { synchronized (this) { // 同步屏障可以建立多个,用计数的 Token 变量识别 final int token = mNextBarrierToken++; // 获取一个屏障 Message // 其 target 属性为空 // 指定 when 属性为屏障的开始时刻 final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; // 将 Token 存入屏障 Message // 用以识别对应的同步屏障 msg.arg1 = token; // 按照 when 的先后 // 找到屏障 Message 插入队列的适当位置 // 所以,如果同步屏障的建立调用得晚 // 那么在它之前的 Message 无法阻拦 Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } // 将屏障 Message 插入 if (prev != null) { msg.next = p; prev.next = msg; } else { // 如果队列尚无 Message // 或队首的 Message 时刻 // 都比屏障 Message 要晚的话 // 将屏障 Message 插入队首 msg.next = p; mMessages = msg; } // 返回上面的 Token 给调用端 // 主要用于移除对应的屏障 return token; } }
再来看下异步 Message 如何执行。
// MessageQueue.java Message next() { ... for (;;) { nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // 队首是屏障 Message 的话 // 遍历找到下一个异步 Message if (msg != null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } // 没有建立同步屏障且队里有 Message // 或者 // 建立了同步屏障下且找到了异步 Message if (msg != null) { // 如果当前时间尚早于目标执行时刻 if (now < msg.when) { // 更新下次循环应当休眠的超时时间 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { mBlocked = false; // Message 找到了并出队 if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } // Message 返回 msg.next = null; msg.markInUse(); return msg; } } else { // 队里尚无 Message // 或建立了同步屏障,但尚无异步 Message // 无限休眠 nextPollTimeoutMillis = -1; } ... } ... pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; } }
最后看一下同步屏障如何移除。
// MessageQueue.java // 需要传入 add 时返回的 Token public void removeSyncBarrier(int token) { synchronized (this) { Message prev = null; Message p = mMessages; // 遍历队列直到找到 token 吻合的屏障 Message while (p != null && (p.target != null || p.arg1 != token)) { prev = p; p = p.next; } // 如果没找到会抛出异常 if (p == null) { throw new IllegalStateException("The specified message queue synchronization " + " barrier token has not been posted or has already been removed."); } final boolean needWake; // 将屏障 Message 移除 // 如果屏障 Message 不在队首的话 // 无需唤醒 if (prev != null) { prev.next = p.next; needWake = false; } else { // 屏障 Message 在队首 // 且新的队首存在且不是另一个屏障的话 // 需要立即唤醒 mMessages = p.next; needWake = mMessages == null || mMessages.target != null; } p.recycleUnchecked(); // 唤醒以立即处理后面的 Message if (needWake && !mQuitting) { nativeWake(mPtr); } } }
对原理进行简单的总结:
同步屏障的建立:按照调用的时刻 when 在合适的位置放入一个屏障 Message(target 属性为 null)来实现,同时得到标识屏障的计数 token 存入屏障 Message
读取队列的时候发现存在屏障 Message 的话,会遍历队列并返回最早执行的异步 Message
同步屏障的移除:按照 token 去队列里找到匹配的屏障 Message 进行出队操作,如果出队后队首存在 Message 且非另一个同步屏障的话,立即唤醒 looper 线程
结论和应用
结论:
可以通过异步 Handler,也可以通过异步 Message 两种方式向 MessageQueue 添加异步 Message
但都需要事先建立同步屏障,屏障的建立时间必须在阻拦的 Message 发出之前
可以建立多个同步屏障,将按照指定的时刻排队,通过计数 Token 进行识别
同步屏障使用完之后记得移除,否则后续的 Message 永远阻塞
和插队执行 Message 的区别:
插队 Message 只能确保先执行,完了后续的 Message 还得执行
异步 Message 则不同,同步屏障一旦建立将保持休眠,直到异步 Message 抵达。只有同步屏障被撤销,后续 Message 才可恢复执行
应用:
AOSP 系统中使用异步 Message 最典型的地方要属屏幕刷新,刷新的 Message 不希望被主线程的 Message 队列阻塞,所以在发送刷新 Message 之前都会建立一个同步屏障,确保刷新任务优先执行。
// ViewRootImpl.java void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } }
屏障建立之后发送异步 Message。
// Choreographer.java private void postCallbackDelayedInternal(...) { synchronized (mLock) { ... if (dueTime <= now) { scheduleFrameLocked(now); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } }
IdleHandler ”Message“
MessageQueue 提供的 IdleHandler 可以让队列在空闲的时候回调(queueIdle())指定的逻辑,它本质上不是 Message 类型,但它在 MessageQueue 里调度的时候类似于 Message 的逻辑,姑且将它也理解成一种特殊的 ”Message“。
使用上很简单,调用 MessageQueue 的 addIdleHandler() 添加实现即可,执行完之后无需再度执行的话需要调用 removeIdleHandler() 移除,或在回调里返回 false。
override fun onCreate(savedInstanceState: Bundle?) { ... testIdleHandler() } private fun testIdleHandler() { Log.d("MainActivity","testIdleHandler() start") mainHandler.looper.queue.addIdleHandler { Log.d("MainActivity", "testIdleHandler() queueIdle callback") false } Log.d("MainActivity","testIdleHandler() end ") }
可以看到 addIdleHandler 调用之后并没有立即执行,而是过了几百 ms,queueIdle() 才得到了执行。
09-23 22:56:46.130 7732 7732 D MainActivity: onCreate() 09-23 22:56:46.281 7732 7732 D MainActivity: testIdleHandler() start 09-23 22:56:46.281 7732 7732 D MainActivity: testIdleHandler() end 09-23 22:56:46.598 7732 7732 D MainActivity: testIdleHandler() queueIdle callback
如果在 addIdleHandler 调用之后接着发送一串非延时 Message,queueIdle() 是先执行还是后执行呢?
override fun onCreate(savedInstanceState: Bundle?) { ... testIdleHandler() testSendMessages() }
结果显示一堆 Message 执行完了之后,仍旧过了几百 ms,queueIdle() 才得到了执行。
09-23 23:07:50.639 7926 7926 D MainActivity: onCreate() 09-23 23:07:50.856 7926 7926 D MainActivity: testIdleHandler() start 09-23 23:07:50.856 7926 7926 D MainActivity: testIdleHandler() end 09-23 23:07:50.856 7926 7926 D MainActivity: startSendMessage() start 09-23 23:07:50.857 7926 7926 D MainActivity: startSendMessage() end 09-23 23:07:50.914 7926 7926 D MainActivity: Main thread message occurred & what:1 ... 09-23 23:07:50.916 7926 7926 D MainActivity: Main thread message occurred & what:10 09-23 23:07:51.132 7926 7926 D MainActivity: testIdleHandler() queueIdle callback
上述结果也可以理解,MessageQueue 里仍有一堆 Message 等待处理,并非空闲状态。所以需要执行完之后才有机会回调 queueIdle() 。
那如果发送的是延时 Message 呢?
override fun onCreate(savedInstanceState: Bundle?) { ... testIdleHandler() testSendDelayedMessages() }
因为发送的是延时 Message,MessageQueue 暂时是空闲的,会先将 IdleHandler 取出来处理。
09-23 23:21:36.135 8161 8161 D MainActivity: onCreate() 09-23 23:21:36.339 8161 8161 D MainActivity: testIdleHandler() start 09-23 23:21:36.340 8161 8161 D MainActivity: testIdleHandler() end 09-23 23:21:36.340 8161 8161 D MainActivity: testSendDelayedMessages() start 09-23 23:21:36.340 8161 8161 D MainActivity: testSendDelayedMessages() end 09-23 23:21:36.729 8161 8161 D MainActivity: testIdleHandler() queueIdle callback 09-23 23:21:38.844 8161 8161 D MainActivity: Main thread message occurred & what:1 ... 09-23 23:21:38.845 8161 8161 D MainActivity: Main thread message occurred & what:10
上面的 queueIdle() 返回了 false 确保处理后 Handler 得到了移除。
但如果返回 true 且没有调用 removeIdleHandler() 的话,后续空闲的时候 Handler 还会被执行,这点需要留意!
private fun testIdleHandler() { mainHandler.looper.queue.addIdleHandler { ... true // false } }
queueIdle() 因为没被移除的缘故被回调了多次,源自于 Looper 没执行完一次 Message 后发现尚无 Message 的时候都会回调一遍 IdleHandler,直到队列一直没有 Message 到来。
09-23 23:24:04.765 8226 8226 D MainActivity: onCreate() 09-23 23:24:05.010 8226 8226 D MainActivity: testIdleHandler() start 09-23 23:24:05.011 8226 8226 D MainActivity: testIdleHandler() end 09-23 23:24:05.368 8226 8226 D MainActivity: testIdleHandler() queueIdle callback 09-23 23:24:05.370 8226 8226 D MainActivity: testIdleHandler() queueIdle callback 09-23 23:24:05.378 8226 8226 D MainActivity: testIdleHandler() queueIdle callback 09-23 23:24:05.381 8226 8226 D MainActivity: testIdleHandler() queueIdle callback 09-23 23:24:05.459 8226 8226 D MainActivity: testIdleHandler() queueIdle callback
那如果 add 完不移除的 IdleHandler 之后,发送一个延时 Message,那便会导致空闲消息多执行一遍。
override fun onCreate(savedInstanceState: Bundle?) { ... testIdleHandler() sendDelayedMessage(mainHandler, 1) }
09-23 23:31:53.928 8620 8620 D MainActivity: onCreate() 09-23 23:31:54.042 8620 8620 D MainActivity: testIdleHandler() start 09-23 23:31:54.042 8620 8620 D MainActivity: testIdleHandler() end 09-23 23:31:54.272 8620 8620 D MainActivity: testIdleHandler() queueIdle callback 09-23 23:31:54.273 8620 8620 D MainActivity: testIdleHandler() queueIdle callback 09-23 23:31:54.278 8620 8620 D MainActivity: testIdleHandler() queueIdle callback 09-23 23:31:54.307 8620 8620 D MainActivity: testIdleHandler() queueIdle callback 09-23 23:31:54.733 8620 8620 D MainActivity: testIdleHandler() queueIdle callback 09-23 23:31:56.546 8620 8620 D MainActivity: Main thread message occurred & what:1 09-23 23:31:56.546 8620 8620 D MainActivity: testIdleHandler() queueIdle callback
为什么?
queueIdle() 的回调由 MessageQueue#next() 回调。
// MessageQueue.java Message next() { ... // 循环的初次将待处理 IdleHandler 计数置为 -1 // 保证第一次可以检查 Idle Handler 的存在和调用 int pendingIdleHandlerCount = -1; int nextPollTimeoutMillis = 0; for (;;) { ... nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // 队首的 Message 且建立了同步屏障的话,寻找下一个异步 Message if (msg != null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } // 找到了合适的 Message if (msg != null) { // 如果当前时间尚早于目标执行时刻 // 设置休眠的超时时间,即当前时间与目标时刻的差值 if (now < msg.when) { nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { mBlocked = false; // 时间条件满足 Message 出队 if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } // 并返回 Message msg.next = null; msg.markInUse(); return msg; } } else { // 队里尚无合适的 Message // 进入无限休眠 nextPollTimeoutMillis = -1; } // 如果正在退出 Looper,结束循环并返回 null // 将促使 loop() 退出 if (mQuitting) { dispose(); return null; } // 如果没有合适的 Message 且 Looper 没有退出 // 检查是否有 Idle Handler 需要处理 // 读取 Idle Handler 列表 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } // 如果暂时没有 Idle Handler 需要处理,则进入下一次循环 // 为使下次循环如果出现新的 Idle Handler 能有机会执行 // 不重置计数器,仍为初始值 -1 if (pendingIdleHandlerCount <= 0) { mBlocked = true; continue; } // 如果 IdleHandler 存在则拷贝到待处理列表 if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // 遍历待处理 Idle Handlers for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; boolean keep = false; try { // 逐个回调 queueIdle() keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } // 回调返回 false,则将其移除出 Idle 列表 if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // 处理完之后重置 IdleHandler 的计数 // 保证下次循环不会重复处理 IdleHandler pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; } }
有几点细节需要留意:
next() 循环的第一次将 count 置为 -1,确保队列空闲的时候必然有机会处理 IdleHandler
如果暂无 IdleHandler 可以处理直接进入下一次循环,并且保留 count 的处置,确保下次循环可以检查是否有新的 IdleHandler 加入进来
IdleHandler 正常处理结束之后,避免下次循环重复处理,会将 count 置为 0,保证下次不再检查。注意:是下次循环,不是永久不检查
结论和应用
结论:
IdleHandler 可以实现 MessageQueue 空闲状态下的任务执行,比如做一些启动时的轻量级初始化任务。但由于其执行的时机依赖于队列的 Message 状态,不太可控,谨慎使用!
应用:AOSP 源码里有不少地方使用了 IdleHandler 机制,比如 ActivityThread 使用它在空闲的状态下进行 GC 回收处理。
// ActivityThread.java final class GcIdler implements MessageQueue.IdleHandler { @Override public final boolean queueIdle() { doGcIfNeeded(); purgePendingResources(); return false; } } void scheduleGcIdler() { if (!mGcIdlerScheduled) { mGcIdlerScheduled = true; Looper.myQueue().addIdleHandler(mGcIdler); } mH.removeMessages(H.GC_WHEN_IDLE); } void unscheduleGcIdler() { if (mGcIdlerScheduled) { mGcIdlerScheduled = false; Looper.myQueue().removeIdleHandler(mGcIdler); } mH.removeMessages(H.GC_WHEN_IDLE); }
名词总结
非延时、延时以及插队执行这几种 Message 大家使用较多,无需赘述。但其他几个冷僻的 Message 术语需要总结一下,供大家快速对比和加深理解。
结语
上述对于各种 Message 和 IdleHandler 做了演示和原理阐述,相信对于它的细节有了更深的了解。
下面来进行一个简单的总结:
非延时执行 Message:并非立即执行,而是按照请求的时刻进行排队和调度,最终取决于队列的顺序和主线程是否空闲
延时执行 Message:也并非在 Delay 的时刻立即执行,执行时刻受唤醒误差和线程任务阻塞的影响必然晚于 Delay 时刻
插队执行 Mesage:同样并非立即执行,而是每次都将任务放在了队首,达到先执行的目的,但打乱了执行顺序存在逻辑隐患
异步 Message:系统使用居多,App 则需反射,通过这种机制可插队执行同时确保其他 Message 阻塞,学习一下
IdleHandler “Message”:系统多有使用,实现 MessageQueue 空闲状态下的任务执行,但执行时机不可控,最好执行完之后便移除,谨慎使用