Handler进阶之sendMessage原理探索

2023-11-09

Handler进阶之sendMessage


 本文主要进一步的探索Handler,主要介绍下Handler是如何发送消息的?
 用过Handler的想必对以下几个方法都不会陌生:

sendMessage(Message msg);//立刻发送消息
sendMessageAtTime(Message msg, long atTime);//在某个时间点发送消息
sendMessageDelayed(Message msg, long delayedTime);//在当前时间点延迟一段时间发送消息

 以上是三个Handler发送消息的方法,区别在于发送的时间点不一致,但其实三个方法在最终都是执行Handler内的同一个方法,只是在参数上稍有区别:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
sendMessage(Message msg);
//对应
sendMessageDelayed(msg, 0)sendMessageAtTime(Message msg, long atTime);
//对应
sendMessageDelayed(msg, atTime)
sendMessageDelayed(Message msg, long delayedTime);
//对应
sendMessageDelayed(msg, SystemClock.uptimeMillis() + delayedTime)

 从上面的代码能够看出,最终的消息发送都是传入了要发送Message对象和对应需要发送Message的时间点。
我们猜想,Handler发送消息难道是根据Message需要发送的时间点设置一个定时器让Message在某个时间点被发送出去?
如果仅此而已的话,你也太小看google的大牛们了。是不是已经有点迫不及待想直到大牛们都是怎么来做的了?


 这里将中间调用的几个简单的过程跳过,熟悉Java的一眼就能看懂,直接进入发送消息时最核心的部分,
我们都知道Handler发送消息其实是将Message先放入到了MessageQueue,能看到该文章的我默认大家都已经很熟悉
Handler的基本原理了,如果又不熟悉的,可以参考

Handler消息机制原理全方面解读

 此处直接看Message放入MessageQueue的过程:

boolean enqueueMessage(Message msg, long when) {
        ...//代码较多,省去了部分抛出异常的代码
        synchronized (this) {
            ...
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

 重点就在if-else这里,将它们一个一个拆开来看:

if (p == null || when == 0 || when < p.when) {//p是当前MessageQueue队首Message
    // New head, wake up the event queue if blocked.
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
}

 如果当前队列没有其他需要发送的Message;或者当前新添加进来的的Message的时间点为0(即需要立即发送的消息);
或者当前新添加进来的的Message需要发送的时间点小与当前MessageQueue队列头部Message的时间点(即当前添加进来的Message需要在当前MessageQueue队列头部Message之前被发送)时,就会进入到if代码块里面。此时做的事情是将当前新添加的Message插入到了MessageQueue的队首(看不懂是如何插入的可以参考Handler消息机制原理全方面解读
Message 的存储是链表的形式,next相当于链表的尾指针)。
 这里还有一个赋值操作(needWake = mBlocked),这里解释下,可以看到代码里也有注释,新的首部,然后如果阻塞了,需要唤醒线程。为什么会有线程的阻塞呢?其实MessageQueue内部的消息是按需要发送的时间点从小到大排列的,后面会分析到,从当前if里的when判断也能看出一二,当队首的Message未到达发送的时间点时,说明其当前所有的消息都未到达发送的时间,上面说过,Handler发送消息并不是通过定时器发送的,所以,当队首Message(最近需要发送的Message)未到达发送时间点时,线程被阻塞,所以这里需要根据线程是否阻塞看是否需要唤醒线程,这样才能使新加入的Message能及时发送出去,不会被阻塞。线程的唤醒是通过native的方法来实现的。
 接着来看下else里面的代码:

                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;

 执行到了else语句里面,说明了当前添加进来的Message是在当前MessqgeQueue队首的Message之后才会被发送的,上边分析if部分代码的时候说过了Message是按需要发送的时间先后排列在MessageQueue中的,这里的for循环实际操作就是找到MessageQueue中比当前添加进来的Message需要发送的时间点大的位置,将Message插入到其前边(实际就是一个链表的插入操作)。
 本章分析到这里就先告一段落了,之后会再详细的分析Message从MessageQueue的取出过程,可以先参考Handler消息机制原理全方面解读


之所以写这篇文章主要是为了解答两个问题。

  1. sendMessageDelayed是如何实现延时发送消息的?
  2. sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message?
总结:
  1. Handler在发送消息的时候,MessageQueue里的消息是按照发送时间点从小到大排列的,
    如果最近的Message未到达发送的时间则阻塞。
  2. 新加入的数据会根据时间点的大小判断需要插入的位置,同时还需要判断是否需要唤醒线程去发送当前的队首的消息。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Handler进阶之sendMessage原理探索 的相关文章

随机推荐

  • TypeScript 基础类型 —— void

    声明为 void 类型表示没有任何类型 当一个函数没有返回值时 通常其返回值会声明为 void 类型 function gretter void console log 123 编译成js function gretter console
  • 使用Python实现K均值聚类算法

    使用Python实现K均值聚类算法 K均值聚类算法是一种经典的无监督学习算法 它将数据集分为K个簇 每个簇中的数据点与同一簇中心点的距离最小 不同簇的数据点之间的距离较大 该算法常用于数据挖掘 图像处理等领域 以下是其优缺点和Python实
  • Electron+Vue入门(二)vue-cli3.0+electron项目初始化

    第一步 用vue cli3 0创建一个项目 打开命令行工具 vue create demo 选择默认 安装完成 第二步 安装vue cli plugin electron builder 进入项目 cd demo 进入vue项目管理器 vu
  • 怎么样理解同步清零和异步清零?

    DA专业论坛 通用设计 求助 大家是怎么样理解 同步清零和 异步清零的 查看完整版本 求助 大家是怎么样理解同步清零和异步清零的 mxflying 2005 4 20 03 45 求助 大家是怎么样理解 同步清零和 异步清零的 本人对 同步
  • ROS-kinetic中Gazebo中的机械臂仿真报错解决

    1 警告 其实是错误 但也要解决 WARN 1682069601 434351 0 000000 Controller Spawner couldn t find the expected controller manager ROS in
  • 有哪些因素影响服务器的访问速度

    在网络环境下 根据服务器提供的服务类型不同 分为文件服务器 数据库服务器 应用程序服务器 WEB服务器等 一些对服务器的了解不够深入的朋友 会认为服务器的配置越高 服务器的访问速度就会越快 这句话有一定的道理 但是服务器的配置高低只是影响服
  • 计算机视觉项目实战-图像特征检测harris、sift、特征匹配

    欢迎来到本博客 本次博客内容将继续讲解关于OpenCV的相关知识 作者简介 目前计算机研究生在读 主要研究方向是人工智能和群智能算法方向 目前熟悉python网页爬虫 机器学习 计算机视觉 OpenCV 群智能算法 然后正在学习深度学习的相
  • android中下拉菜单的制作(详解)

    在我们的android中下拉菜单的制作有两种的方法 1 一种的方式就是通过我们的布局文件的方法制作 2 第二种方式就是通过我们的java代码的方式制作 第一种方式
  • deepin 20.2版本亮度调节问题暂时解决方案

    可在设置 gt 键盘和语言 gt 快捷键 中设置自己需要的快捷键 建议alt 1和alt 2这两个 与现有快捷键没有冲突 使用原来的快捷键会提示冲突 如果覆盖了设置可能会使原来的快捷键失效 分别添加下面的命令 降低亮度 echo your
  • Anaconda 换源与更新

    参考 Windows下Anaconda安装 换源与更新 里面很详细介绍了 conda 的更新 与 Anaconda 的更新
  • Node.js入门笔记(一)——环境问题和版本号问题

    Node js入门笔记 一 1 node js的版本管理工具 nvm 2 npm全局安装和局部安装 3 开发环境安装与生产环境安装 4 其他常用的npm语法 5 版本号里面的讲究 6 npm上传包 其实就是寒假比较无聊搭了这个自己的博客网站
  • Visual Studio+VAssistX自动添加注释

    1 增加函数头注释 右击函数名 然后依次点击 Refacto gt Document Method 这个时候函数头注释就会蹦出来 不过这个注释的格式是默认的 想修改注释格式 可以通过以下方法 点击 VAssistX gt Visual VA
  • IE下载文件时,中文文件名乱码问题

    经排查 Content Disposition中的filename进行了两次URL转码 以汉字漫为例 第一次转码 漫变为 E6 BC AB 第二次转码 E6 BC AB变为 25E6 25BC 25AB 第二次转码时 因为 是特殊字符 所以
  • word2vector学习笔记(一)

    word2vector学习笔记 一 最近研究了一下google的开源项目word2vector http code google com p word2vec 其实这玩意算是神经网络在文本挖掘的一项成功应用 本文是看了论文 Distribu
  • Spring中如何在一个Bean中注入一个内部Bean呢?

    转自 Spring中如何在一个Bean中注入一个内部Bean呢 在日常开发中 有些实体类的定义 一个类中包含了另一个类 那么在Spring Bean中 同样也有此种操作 下文将讲述使用xml配置文件的方式注入内部bean的方法 实现思路 使
  • 经典面试题 为什么要用 Docker

    经典面试题 为什么要用 Docker 解决面试题 斩获心仪的 Offer 文章目录 经典面试题 为什么要用 Docker 一 Docker是什么 二 Docker 的优势 1 更高效的利用系统资源 2 更快速的启动时间 3 一致的运行环境
  • T检验python实现

    from scipy import stats import numpy as np 方差齐性检验 方差反映了一组数据与其平均值的偏离程度 方差齐性检验用以检验两组或多组数据与其均值偏离程度是否存在差异 也是很多检验和算法的先决条件 np
  • 深度学习之基于Xception实现四种动物识别

    本次实验类似于猫狗大战 只不过将两种动物识别变为了四种动物识别 本文的重点是卷积神经网络Xception的实践 在之前的学习中 我们已经实验过其他几种比较常用的网络模型 但是Xception网络并未实践过 在弄本科毕设的时候 一个好朋友的毕
  • bukku ctf(刷题2)

    bugku ctf 抄错的字符 简单取证1 这是一张单纯的图片 计算器 GET POST 矛盾 alert 你必须让他停下 signin Easy Re 游戏过关 Easy vb 树木的小秘密 Timer 阿里CTF 抄错的字符 Crypt
  • Handler进阶之sendMessage原理探索

    Handler进阶之sendMessage 本文主要进一步的探索Handler 主要介绍下Handler是如何发送消息的 用过Handler的想必对以下几个方法都不会陌生 sendMessage Message msg 立刻发送消息 sen