万字复盘 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 进队列
  • 接着执行主线程的其他任务,比如日志的打印
  • 空闲后 wake 完毕并在 next() 的下一次循环里将队首 Message 移除和返回给 Looper 去回调和执行
  • 之后 loop() 开始读取 MessageQueue 当前队首 Message 的下一次循环,当前时刻必然晚于 send 时候设置的when,所以队列里的 Message 挨个出队和回调

结论

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

MessageQueue 会记录请求的时刻,按照时刻的先后顺序进行排队。如果 MessageQueue 中积攒了很多 Message,或主线程被占用的话,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 后线程存在其他任务导致执行更加延后。

// 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 术语 介绍
异步 Message isAsync 属性为 true 需要异步执行的 Message,需要配合同步屏障使用
异步 Handler 专门发送异步 Message 的 Handler
屏障 Message target 为空并持有 token 信息的 Message 实例放入队列,作为同步屏障的起点
同步屏障 在 MessageQueue 指定时刻插入屏障 Message 确保只有异步 Message 执行的机制
空闲 IdleHandler 用于在 MessageQueue 空闲的时候回调的处理接口,若不移除每次队列空闲了均会执行

结语

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

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

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

猜你喜欢

转载自juejin.im/post/7016728431723282463