阅读 574

万字复盘 Handler 中各式 Message 的使用和原理

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文同时参与「掘力星计划」,赢取创作大礼包,挑战创作激励金!

我们会经常使用 Handlersendpost 一个延时非延时插队执行的 Message,但对于这个 Message 到底什么时候执行以及为什么是这样,鲜少细究过。本文将一 一盘点,并起底个中原理!

同时针对大家疏于了解的异步执行 Message 和空闲执行 IdleHandler,进行演示和原理普及。篇幅较大,搭配收藏食用更佳~

目录一览:

  • 非延时执行 Message
  • 延时执行 Message
  • 插队执行 Message
  • 异步执行 Message
  • 空闲执行 “Message”
  • 名词回顾

非延时执行 Message

12-widget

先在主线程创建一个 Handler 并复写 Callback 处理。

    private val mainHandler = Handler(Looper.getMainLooper()) { msg ->
        Log.d(
            "MainActivity",
            "Main thread message occurred & what:${msg.what}"
        )
        true
    }
复制代码

不断地发送期望即刻执行的 Message 和 Runnable 给主线程的 Handler。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        testSendNoDelayedMessages()
    }

    private fun testSendNoDelayedMessages() {
        Log.d("MainActivity","testSendNoDelayedMessages() start")
        testSendMessages()
        testPostRunnable()
        Log.d("MainActivity","testSendNoDelayedMessages() end ")
    }

    private fun testSendMessages() {
        Log.d("MainActivity","startSendMessage() start")
        for (i in 1..10) {
            sendMessageRightNow(mainHandler, i)
        }
        Log.d("MainActivity","startSendMessage() end ")
    }

    private fun testPostRunnable() {
        Log.d("MainActivity","testPostRunnable() start")
        for (i in 11..20) {
            mainHandler.post { Log.d("MainActivity", "testPostRunnable() run & i:${i}") }
        }
        Log.d("MainActivity","testPostRunnable() end ")
    }
复制代码

什么时候执行?

公布下日志前,大家可以猜测下运行的结果,Message 或 Runnable 在 send 或 post 之后会否立即执行。不是的话,什么时候执行?

 D MainActivity: testSendNoDelayedMessages() start
 D MainActivity: startSendMessage() start
 D MainActivity: startSendMessage() end
 D MainActivity: testPostRunnable() start
 D MainActivity: testPostRunnable() end
 D MainActivity: testSendNoDelayedMessages() end
 D MainActivity: Main thread message occurred & what:1
 ...
 D MainActivity: Main thread message occurred & what:10
 D MainActivity: testPostRunnable() run & i:11
 ...
 D MainActivity: testPostRunnable() run & i:20
复制代码

答案可能跟预想的略有出入,但一细想好像又是合理的:发送完的 Message 或 Runnable 不会立即执行MessageQueue 的唤醒和回调需要等主线程的其他工作完成之后才能执行。

为什么?

非延时的 sendMessage()post() 最终仍然是调用 sendMessageAtTime() 将 Message 放入了 MessageQueue。只不过它的期待执行时间 when 变成了 SystemClock.uptimeMillis(),即调用的时刻。

// Handler.java
    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); // when 等于当前时刻
    }

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        return enqueueMessage(queue, msg, uptimeMillis);
    }
复制代码

这些 Message 会按照 when 的先后排队进入 MessageQueue 中,当 Message 满足了条件会立即调用 wake,反之只是插入队列而已。所以,上述的 send 或 post 循环,会按照调用的先后挨个进入队列,第一个 Message 会触发 wake。

// MessageQueue.java
    boolean enqueueMessage(Message msg, long when) {
        ...
        // 鉴于多线程往 Handler 里发送 Message 的情况
        // 在向队列插入 Message 前需要上锁
        synchronized (this) {
            ...
            msg.markInUse(); // Message 标记正在使用
            msg.when = when; // 更新 when 属性
            Message p = mMessages; // 拿到队列的 Head 
            boolean needWake;
            // 如果队列为空
            // 或者 Message 需要插队(sendMessageAtFrontOfQueue)
            // 又或者 Message 执行时刻比 Head 的更早
            // 该 Message 插入到队首
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                // 线程是否因为没有可执行的 Message 正在 block 或 wait
                // 是的话,唤醒
                needWake = mBlocked;
            } else {
                // 如果队列已有 Message,Message 优先级又不高,同时执行时刻并不早于队首的 Message
                // 如果线程正在 block 或 wait,或建立了同步屏障(target 为空),并且 Message 是异步的,则唤醒
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // 遍历队列,找到 Message 目标插入位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    // 如果已经遍历到队尾了,或 Message 的时刻比当前 Message 要早
                    // 找到位置了,退出遍历
                    if (p == null || when < p.when) {
                        break;
                    }
                    
                    // 如果前面决定需要唤醒,但队列已有执行时刻更早的异步 Message 的话,先不唤醒
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 将 Message 插入队列的目标位置
                msg.next = p;
                prev.next = msg;
            }

            // 需要唤醒的话,唤醒 Native 侧的 MessageQueue
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
复制代码

总结来讲:

  • 第一次 send 的 Message 在 enqueue 进 MessageQueue 的队首后,通知 Native 侧 wake
  • 后续发送的其他 Message 或 Runnable 挨个 enqueue 进队列
  • 接着执行主线程的其他 Message,比如日志的打印
  • 空闲后 wake 完毕并在 next() 的下一次循环里将队首 Message 移除和返回给 Looper 去回调和执行
  • 之后 loop() 开始读取 MessageQueue 当前队首 Message 的下一次循环,当前时刻必然晚于 send 时候设置的when,所以队列里的 Message 挨个出队和回调

结论

非延时 Message 并非立即执行,只是放入 MessageQueue 等待调度而已,执行时刻不确定。

MessageQueue 会记录请求的时刻,按照时刻的先后顺序进行排队。如果 MessageQueue 中积攒了很多 Message,或主线程被占用的话,Message 的执行会明显晚于请求的时刻。

比如在 onCreate() 里发送 message 的话,你会发现当 onResume() 执行完才会回调你的 Message。原因在于 onCreate() 等操作也是由 Message 触发,其同步处理完 onResume() 之后,才有机会进入下一次循环去读取你的 Message。

延时执行 Message

12-widget

延时执行的 Message 使用更为常见,那它又是何时执行呢?

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        testSendDelayedMessages()
    }

    private fun testSendDelayedMessages() {
        Log.d("MainActivity","testSendDelayedMessages() start")
        // 发送 Delay 2500 ms 的 Message
        sendDelayedMessage(mainHandler, 1)
        Log.d("MainActivity","testSendDelayedMessages() end ")
    }
复制代码

28:58.186 发送 Message,29:00.690 Message 执行,时间差为 2504ms,并非准确的 2500ms。

09-22 22:28:57.964 24980 24980 D MainActivity: onCreate()
09-22 22:28:58.186 24980 24980 D MainActivity: testSendDelayedMessages() start
// 发送 Message
09-22 22:28:58.186 24980 24980 D MainActivity: testSendDelayedMessages() end
// Message 执行
09-22 22:29:00.690 24980 24980 D MainActivity: Main thread message occurred & what:1
复制代码

如果连续发送 10 个均延时 2500ms 的 Message 会怎么样?

    private fun testSendDelayedMessages() {
        Log.d("MainActivity","testSendDelayedMessages() start")
        // 连续发送 10 个 Delay 2500 ms 的 Message
        for (i in 1..10) {
            sendDelayedMessage(mainHandler, i)
        }
        Log.d("MainActivity","testSendDelayedMessages() end ")
    }
复制代码

第 1 个 Message 执行的时间差为 2505ms(39:56.841 - 39:54.336),第 10 个 Message 执行的时间差已经达到了 2508ms(39:56.844 - 39:54.336)。

09-22 22:39:54.116 25104 25104 D MainActivity: onCreate()
09-22 22:39:54.336 25104 25104 D MainActivity: testSendDelayedMessages() start
09-22 22:39:54.337 25104 25104 D MainActivity: testSendDelayedMessages() end
09-22 22:39:56.841 25104 25104 D MainActivity: Main thread message occurred & what:1
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:2
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:3
..
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:8
09-22 22:39:56.842 25104 25104 D MainActivity: Main thread message occurred & what:9
09-22 22:39:56.844 25104 25104 D MainActivity: Main thread message occurred & what:10
复制代码

为什么?

延时 Message 执行的时刻 when 采用的是发送的时刻和 Delay 时长的累加,基于此排队进 MessageQueue。

// Handler.java
    public final boolean postDelayed(Runnable r, int what, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {  
    	return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); 
    }

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        return enqueueMessage(queue, msg, uptimeMillis); 
    }
复制代码

Delay Message 尚未抵达的时候,MessageQueue#next() 会将读取队列的时刻与 when 的差值,作为下一次通知 Native 休眠的时长。进行下一次循环前,next() 还存在其他逻辑,导致 wake up 的时刻存在滞后。此外由于 wake up 后线程存在其他 Message 占用,导致执行更加延后。

// MessageQueue.java
    Message next() {
        ...
        for (;;) {
            ...
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                ...
                
                if (msg != null) {
                    // 计算下一次循环应当休眠的时长
                    if (now < msg.when) {
                        nextPollTimeoutMillis
                            = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        ...
                    }
                } else {
                    ...
                }
                ...
            }
            ...
        }
    }
复制代码

结论

由于唤醒时长的计算误差和回调的任务可能占用线程,导致延时执行 Message 不是时间到了就会执行,其执行的时刻必然晚于 Delay 的时刻。

插队执行 Message

12-widget

Handler 还提供了 Message 插队的 API:sendMessageAtFrontOfQueue()postAtFrontOfQueue()

在上述的 send 和 post 之后同时调用 xxxFrontOfQueue 的方法,Message 的执行结果会怎么样?

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        testSendNoDelayedMessages()
        testFrontMessages() // 立马调用 FrontOfQueue 的方法
    }
复制代码

分别调用 sendMessageAtFrontOfQueue() 和 postAtFrontOfQueue() 的 API。

    private fun testFrontMessages() {
        Log.d("MainActivity","testFrontMessages() start")
        testSendFrontMessages()
        testPostFrontRunnable()
        Log.d("MainActivity","testFrontMessages() end ")
    }

    private fun testSendFrontMessages() {
        Log.d("MainActivity","testSendFrontMessages() start")
        for (i in 21..30) {
            sendMessageFront(mainHandler, i)
        }
        Log.d("MainActivity","testSendFrontMessages() end ")
    }

    private fun testPostFrontRunnable() {
        Log.d("MainActivity","testPostFrontRunnable() start")
        for (i in 31..40) {
            mainHandler.postAtFrontOfQueue() { Log.d("MainActivity", "testPostFrontRunnable() run & i:${i}") }
        }
        Log.d("MainActivity","testPostFrontRunnable() end ")
    }
复制代码

当主线程的打印日志按序输出后,Message 开始逐个执行。按照预想的一样,FrontOfQueue 的 Message 会先执行,也就是最后一次调用这个 API 的最早回调。

Front 的 Message 逆序执行完毕之后,普通的 Message 才按照请求的顺序执行。

 D MainActivity: testSendNoDelayedMessages() start
 D MainActivity: startSendMessage() start
 D MainActivity: startSendMessage() end
 D MainActivity: testPostRunnable() start
 D MainActivity: testPostRunnable() end
 D MainActivity: testSendNoDelayedMessages() end
 D MainActivity: testFrontMessages() start
 D MainActivity: testSendFrontMessages() start
 D MainActivity: testSendFrontMessages() end
 D MainActivity: testPostFrontRunnable() start
 D MainActivity: testPostFrontRunnable() end
 D MainActivity: testFrontMessages() end
 D MainActivity: testPostFrontRunnable() run & i:40
 ...
 D MainActivity: testPostFrontRunnable() run & i:31
 D MainActivity: Main thread message occurred & what:30
 ...
 D MainActivity: Main thread message occurred & what:21
 D MainActivity: Main thread message occurred & what:1
 ...
 D MainActivity: Main thread message occurred & what:10
 D MainActivity: testPostRunnable() run & i:11
 ...
 D MainActivity: testPostRunnable() run & i:20
复制代码

怎么实现的?

原理在于 sendMessageAtFrontOfQueue() 或 postAtFrontOfQueue() 发送的 Mesage 被记录的 when 属性被固定为 0

// Handler.java
    public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
        return enqueueMessage(queue, msg, 0); // 发送的 when 等于 0
    }

    public final boolean postAtFrontOfQueue(@NonNull Runnable r) {
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }
复制代码

从入队函数可以看出,when 为 0 的 Message 会立即插入队首,所以总会先得到执行。

// MessageQueue.java
    enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            ...
            // 如果 Message 需要插队(sendMessageAtFrontOfQueue)
            // 则插入队首
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
            } else {
                ...
            }
            ...
        }
        return true;
    }
复制代码

结论

sendMessageAtFrontOfQueue() 和 postAtFrontOfQueue() 的 API 通过将 when 预设为 0 进而插入 Message 至队首,最终达到 Message 先得到执行的目的。

但需要注意的是,这将造成本来先该执行的 Message 被延后调度,对于存在先后关系的业务逻辑来说将可能造成顺序问题,谨慎使用!

异步执行 Message

12-widget

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);
            }
        }
    }
复制代码

对原理进行简单的总结:

  1. 同步屏障的建立:按照调用的时刻 when 在合适的位置放入一个屏障 Message(target 属性为 null)来实现,同时得到标识屏障的计数 token 存入屏障 Message
  2. 读取队列的时候发现存在屏障 Message 的话,会遍历队列并返回最早执行的异步 Message
  3. 同步屏障的移除:按照 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);
            }
        }
    }
复制代码

空闲执行 “Message”

12-widget

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;
        }
    }
复制代码

有几点细节需要留意:

  1. next() 循环的第一次将 count 置为 -1,确保队列空闲的时候必然有机会处理 IdleHandler
  2. 如果暂无 IdleHandler 可以处理直接进入下一次循环,并且保留 count 的处置,确保下次循环可以检查是否有新的 IdleHandler 加入进来
  3. 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 术语介绍
异步 MessageisAsync 属性为 true 需要异步执行的 Message,需要配合同步屏障使用
异步 Handler专门发送异步 Message 的 Handler
屏障 Messagetarget 为空并持有 token 信息的 Message 实例放入队列,作为同步屏障的起点
同步屏障在 MessageQueue 指定时刻插入屏障 Message 确保只有异步 Message 执行的机制
空闲 IdleHandler用于在 MessageQueue 空闲的时候回调的处理接口,若不移除每次队列空闲了均会执行

结语

上述对于各种 Message 和 IdleHandler 做了演示和原理阐述,相信对于它的细节有了更深的了解。

下面来进行一个简单的总结:

  • 非延时执行 Message:并非立即执行,而是按照请求的时刻进行排队和调度,最终取决于队列的顺序和主线程是否空闲
  • 延时执行 Message:也并非在 Delay 的时刻立即执行,执行时刻受唤醒误差和线程任务阻塞的影响必然晚于 Delay 时刻
  • 插队执行 Mesage:同样并非立即执行,而是每次都将任务放在了队首,达到先执行的目的,但打乱了执行顺序存在逻辑隐患
  • 异步 Message:系统使用居多,App 则需反射,通过这种机制可插队执行同时确保其他 Message 阻塞,学习一下
  • IdleHandler “Message”:系统多有使用,实现 MessageQueue 空闲状态下的任务执行,但执行时机不可控,最好执行完之后便移除,谨慎使用
文章分类
Android
文章标签