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


相关文章
|
算法 网络安全 数据安全/隐私保护
【密码学】手摸手带你手算AES
本文带着大家手动计算了一下完整的简化版AES的整个流程,其实主要都参考了密码学与网络安全这本书,大部分的公式都是从这本书上来的,我是真的喜欢这个例子,麻雀虽小,五脏俱全,用来学习AES的入门感觉非常的合适,如果能够完整的自己手算下来这个例子,然后再去看完整版的AES算法,会简单非常多,主要就是规模扩大了一下,核心的流程还是上面这一套。最后,感谢能看到这里的读者,如果本文对大佬们理解AES有一点点的帮助,也不枉我手动敲了这么多的公式和矩阵了。
1530 0
【密码学】手摸手带你手算AES
|
Java Maven Android开发
Android 阿里云镜像整理
Android 阿里云镜像整理
8517 0
|
4月前
|
人工智能 运维 前端开发
Claude Code 30k+ star官方插件,小白也能写专业级代码
Superpowers是Claude Code官方插件,由核心开发者Jesse打造,上线3个月获3万star。它集成brainstorming、TDD、系统化调试等专业开发流程,让AI写代码更规范高效。开源免费,安装简单,实测显著提升开发质量与效率,值得开发者尝试。
12427 5
|
11月前
|
监控 API 开发工具
HarmonyOS Next的HiLog日志系统完全指南:从入门到精通
本文深入解析HarmonyOS Next的HiLog日志系统,涵盖日志级别、核心API、隐私保护与高级回调功能,助你从入门到精通掌握这一重要开发工具。
|
Java 应用服务中间件 Maven
idea配置本地maven保姆级教程
idea配置本地maven保姆级教程
3593 0
|
开发框架 .NET PHP
网站应用项目如何选择阿里云服务器实例规格+内存+CPU+带宽+操作系统等配置
对于使用阿里云服务器的搭建网站的用户来说,面对众多可选的实例规格和配置选项,我们应该如何做出最佳选择,以最大化业务效益并控制成本,成为大家比较关注的问题,如果实例、内存、CPU、带宽等配置选择不合适,可能会影响到自己业务在云服务器上的计算性能及后期运营状况,本文将详细解析企业在搭建网站应用项目时选购阿里云服务器应考虑的一些因素,以供参考。
|
Java 应用服务中间件 Maven
SpringBoot项目打包成war包
通过上述步骤,我们成功地将一个Spring Boot应用打包成WAR文件,并部署到外部的Tomcat服务器中。这种方式适用于需要与传统Servlet容器集成的场景。
1774 8
|
Java Maven Spring
springboot学习一:idea社区版本创建springboot项目的三种方式(第三种为主)
这篇文章介绍了在IntelliJ IDEA社区版中创建Spring Boot项目的三种方法,特别强调了第三种方法的详细步骤。
16634 0
springboot学习一:idea社区版本创建springboot项目的三种方式(第三种为主)
|
网络协议 Docker 容器
docker中的DNS配置
【10月更文挑战第5天】
3986 1