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

简介: 万字复盘 Handler 中各式 Message 的使用和原理(1)

我们会经常使用 Handler 的 send 或 post 去安排一个延时、非延时或插队执行的 Message。但对于这个 Message 到底什么时候执行以及为什么是这样,鲜少细究过。


本文将一 一盘点并起底个中原理!


同时针对大家不太熟悉的异步 Message 和 IdleHandler,进行演示和原理普及,篇幅较大,慢慢享用。

非延时执行 Message

1832b220aa754cd18c504acc7686a560.png先在主线程创建一个 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

1832b220aa754cd18c504acc7686a560.png

延时执行的 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

1832b220aa754cd18c504acc7686a560.png

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 被延后调度,对于存在先后关系的业务逻辑来说将可能造成顺序问题,谨慎使用!


相关文章
|
11月前
|
Java Android开发
Handler 中的奥秘
Handler 中的奥秘
35 0
|
3月前
|
前端开发
【源码共读】如何优雅的处理 Promise 的错误
【源码共读】如何优雅的处理 Promise 的错误
65 0
|
前端开发 JavaScript
web前端面试高频考点——JavaScript 篇(二)【JS 异步进阶】Event Loop、then 和 catch、async/await、宏任务微任务、手撕 Promise 源码
web前端面试高频考点——JavaScript 篇(二)【JS 异步进阶】Event Loop、then 和 catch、async/await、宏任务微任务、手撕 Promise 源码
160 0
|
JavaScript 前端开发 API
每个 Bug 都值得认真对待:分享一个 debug 的案例,推荐给前端实习生参考
每个 Bug 都值得认真对待:分享一个 debug 的案例,推荐给前端实习生参考
260 0
|
Java API 调度
万字复盘 Handler 中各式 Message 的使用和原理(2)
万字复盘 Handler 中各式 Message 的使用和原理(2)
万字复盘 Handler 中各式 Message 的使用和原理(2)
|
测试技术
软件测试面试题:遇到frame框架页面怎么处理?
软件测试面试题:遇到frame框架页面怎么处理?
164 0
|
人工智能 安全 Java
第十章 Channel--第四天 完结
channel用于goroutine之间的通讯. 其内部实现了同步, 确保并发安全, 多个goroutine同时访问, 不需要加锁.
382 0
第十章 Channel--第四天 完结
|
JavaScript C++
阅读node源码后,如何写出让面试官满意的发布订阅模式?
阅读node源码后,如何写出让面试官满意的发布订阅模式?
215 0
|
IDE Java 编译器
与面试官聊try-catch-finally关闭资源,你的答案还是10年前的?
与面试官聊try-catch-finally关闭资源,你的答案还是10年前的?
146 0