Android---彻底掌握 Handler

Handler 现在几乎是 Android 面试的必问知识点,大多数 Adnroid 工程师都在项目中使用过 Handler。主要场景是子线程完成耗时操作的过程中,通过 Handler 向主线程发送消息 Message,用来刷新 UI 界面。

下面我们来了解 Handler 的发送消息和处理消息的源码实现。分析源码的时候最好找到一个合适的切入点,Handler 源码的一个切入点就是它的默认构造器。

从 new Handler 开始

在无参的构造函数里调用重载的方法,并分别传入 null 和 false。并且在构造方法中给两个全局变量赋值,mLooper 和 mQueue。

mLooper 和 mQueue 都是通过 Looper 来获取,具体代码如下

可以看出,mQueue 是 Looper 中的一个全局变量,类型是 MessageQueue 类

这个 Looper 是什么?何时被初始化?

Looper 介绍

不知大家在开发的时候有没有思考过这么一个问题呢?启动一个 Java 程序的入口函数是 main 方法,当 main 方法执行完毕之后此程序停止运行,也就是进程会自动终止。但是,当打开一个 Activity 之后,只要不按下返回键 Activity 会一直显示在屏幕上。也就是 Activity 所在进程会一直处于运行状态。实际上,Looper 内部维护一个无限循环,保证 App 进程持续进行

Looper 初始化

ActivityThread.java 的 mian 方法是一个新 app 进程的入口,其具体实现如下

图中1处就是初始化当前进程的 Looper 对象;图中2处调用 Looper 的 loop() 方法开启无限循环。

prepareMainLooper() 方法如下

图中1处,在 prepareMainLooper() 方法中调用 prepare() 方法创建 Looper 对象,将 new 出的 Looper 设置到线程本地变量 sThreadLocal 中,也就是说创建的 Looper 与当前线程发生了绑定。

Looper 的构造方法如下

可以看出在构造方法中,初始化了消息队列 MessageQueue 对象。

Looper 方法执行完之后会在图中3处执行 myLooper() 方法从 sThreadLocal 中取出 Looper 对象并复制给 sMainLooper 对象。图中2处在创建 Looper 对象之前会判断 sThreadLocal 中是否已经绑定过 Looper 对象,如果是则抛出异常。这行代码的目的是确保在一个线程中 Looper.prepare() 方法只能被调用1次。比如执行如下代码,程序秒崩

打印日志如下

不是说调用2次 prepare 才会抛异常吗?为什么 MainActivity 中只调用了1遍就导致程序崩溃?

这是因为,在 MainActivity 所在进程被创建时,Looper 的 prepare 方法已经在 main 方法中调用了1遍,这会导致一个非常重要的结果:

\bullet prepare 方法在一个线程中只能被调用1次;

\bullet Looper 的构造方法在一个线程中只能被调用1次;

\bullet 最终导致 MessageQueue 在一个线程中只会被初始化1次

也就是说,UI 线程中只会存在一个 MessageQueue 对象。后续我们通过 Handler 发送的消息都会发送到这个 MessageQueue 当中。

Looper 负责做什么事情

用一句话总结就是,Looper 做的事情是不断从 MessageQueue 中取出 Message,然后处理 Message 中指定的任务

在 ActivityThread 的 main 方法中,除了调用 Looper.prepareMainLooper() 初始化 Looper 对象之外,还调用了 Looper.loop() 方法开启了无限循环。Looper 的主要功能就是在这个循环中完成的。

很显然,loop() 方法执行了一个死循环,这也是一个 Android app 进程能够持续运行的原因。图中1处不断调用 MessageQueue 的 next 方法取出 message。如果 message 不为 null,则调用图中2处进行后续处理。具体就是从 message 中取出 target 对象,然后调用其 dispatchMessage() 方法处理 message 自身。那么这个 target 是谁呢?

查看 Message.java 源码,可以看出 target 就是 Handler 对象,如下所示

Handler 的 dispatchMessage() 方法

可以看出,在 dispatchMessage 方法中调用一个空方法 handleMessage(),而这个方法正是我们创建 Handler 时需要复写的方法那么 Handler 是何时将其设置为一个 message 的 target 呢?(在 Handler 的 enqueueMessage 方法中)

Handler 的 sendMessage() 方法

Handler 有几个重载的 sendMessage() 方法,但是基本都大同小异,代码具体如下

经过几层调用之后,sendMessage 最终会调用 enqueueMessage() 方法,将 message 插入到消息队列 MessageQueue 当中。这个消息队列就是我们上面分析的,在 ActivityThread 的 main 方法中通过 Looper 创建的 MessageQueue。

Handler 的 enqueueMessage 方法

可以看出,在图中1处 enqueueMessage 方法中将 Handler 自身设置为 Message 的 target 对象。因此,后续 message 会调用此 Handler 的 dispatchMessage 来处理。图中2处会判断,如果 message 中的 target 没有被设置,则直接抛出异常。图中3处会按照 Message 的时间(when)来有序的插入 MessageQueue 中。可以看出 MessageQueue 实际上是一个有序队列,只不过是按照 message 的执行时间来排序的。

至此,Handler 的发送消息和消息处理流程已经介绍完毕。

有关 Handler 的面试题

1. Handler 的 post(Runnable) 与 sendMessage() 有什么区别?

看一下 post(Runnable) 的实现源码,如下

实际上 post(Runnable) 会将 Runnable 复制到 message 的 callback 变量中。那么这个 Runnable 是在什么地方被执行的呢?Looper 从 MessageQueue 中取出 Message 之后,会调用 dispatchMessage 方法进行处理。再看一下其实现

可以看出 dispatchMessage 分两种情况,如果 message 的 callback 不为null,一般为通过 Post(Runnable) 方式,会直接执行 Runnable 的 run 方法。因此这里的 Runnable 实际上就是一个回调接口,和线程 Thread 没有任何关系;如果 message 的 callback 为 null,这种一般为 sendMessage 方式,用 Handler 的 handleMessage() 方法进行处理。

2. Looper.loop() 为什么不会阻塞主线程

上面我们提到,Looper 中的 loop 方法实际上是一个死循环,但是我们的 UI 线程却并没有被阻塞,反而还能够进行各种手势操作,这是为什么呢?

在 MessageQueue 的 next 方法中,有如下一段代码

nativePollOnce 方法是一个 native 方法,当调用此 native 方法时,主线程会释放 CPU 资源进入休眠状态,直到下一条消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。

3. Handler 的 sendMessageDelayed 或者 postDelayed 是如何实现的

在向 MessageQueue 队列中插入 Message 时,会根据 Message 的执行时间排序,而消息的延时处理的核心实现是在获取 Message 的阶段。接下来看一下 MessageQueue 的 next 方法

图中蓝框处表示从 MessageQueue 中取出一个 Message,但是当前的系统时间小于 message.when,因此会计算一个 TimeOut 。目的是为了实现在 TimeOut 后再将 UI 线程唤醒。因此后续处理 TimeOut 的代码,只会在 TimeOut 时间后才会被 CPU 执行。

注意,在上述代码中也能看出。如果当前系统时间大于或等于 Message.when,那么会返回 Message 给 Looper.loop(),但是这个逻辑只能保证在 when 之前消息不被处理,不能保证一定在 when 时被处理。

总结

\bullet 应用启动是从 ActivityThread 的 main 开始的。先是执行了 Looper.prepare(),该方法先是 nwe 了一个 Looper 对象,又在私有的构造方法中创建了 MessageQueue 作为此 Looper 对象的成员变量。Looper 对象通过 ThreadLocal 绑定在 MainThread 中。

\bullet 当创建 Handler 子类对象时,在构造方法中通过 ThreadLocal 获取绑定的 Looper 对象,并获取此 Looper 对象的成员变量 MessageQueue 作为该 Handler 对象的成员变量。

\bullet 在子线程中调用上一步创建的 Handler 子类对象的 sendMessage(msg)方法时,在该方法中将 msg 的 target 属性设置为自己本身,同时调用成员变量 MessageQueue 对象的 enqueueMessage() 方法将 msg 放入 MessageQueue 中。

\bullet 主线程创建好之后,会执行 Looper.loop() 方法,该方法获取与线程绑定的 Looper 对象,继而获取该 Looper 对象的成员变量 MessageQueue 对象。并开启一个会阻塞(不占用资源)的死循环,只要 MessageQueue 中有 msg,就会获取该 msg 并执行 msg.target.dispatchMessage(msg) 方法(msg.target 即上一步引用的 handler 对象),此方法中调用了第二步创建 handler 子类对象时覆写的 handleMessage() 方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/116828.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络性能瓶颈分析,让我来说给你听!

在性能测试中,谈到网络问题,其实,在没有特别说明的情况下,我们一般讲的都是 HTTP 协议下的网络瓶颈问题,那,对于这个问题,我们如何来分析呢?计算机中的网络,跟我们现实生…

npm的使用

package.json 快速生成package.json npm init -y “version”: “~1.1.0” 格式为:「主版本号. 次版本号. 修订号」。 修改主版本号是做了大的功能性的改动 修改次版本号是新增了新功能 修改修订号就是修复了一些bug dependencies "dependencies": {&…

陕西某小型水库雨水情测报及大坝安全监测项目案例

项目背景 根据《陕西省小型病险水库除险加固项目管理办法》、《陕西省小型水库雨水情测报和大坝安全监测设施建设与运行管理办法》的要求,为保障水库安全运行,对全省小型病险水库除险加固,建设完善雨水情测报、监测预警、防汛道路、通讯设备、…

Spring源码编译步骤

Spring源码学习 一、Gradle 为什么下载gradle呢?我们平时不都是用maven吗?原因只有一个,spring源码是用gradle构建的,所以,你想看spring源码必须安装和学会使用gradle,那么,让我们开始gradle之…

windows 用vs创建cmake工程并编译opencv应用项目生成exe流程简述

目录 前言一、安装opencv(1)下载(2)双击安装(3)环境变量和system文件夹设置 二、打开vs创建项目三、编辑cpp,.h,cmakelist.txt文件(1)h文件(2&…

ElementuiPlus的table组件实现行拖动与列拖动

借助了插件sortablejs。这种方法只适合做非树状table。如果想实现树状table&#xff0c;并且可拖动。可以试一下aggridVue3这个插件 <template><div class"draggable" style"padding: 20px"><el-table row-key"id" :data"t…

云安全—docker Deamon攻击面

0x00 前言 本篇文章主要是讲docker Deamon的原理以及docker Deamon攻击面相关的内容&#xff0c;属于抛砖引玉系列&#xff0c;如有不妥之处还请斧正。 0x01 docker Deamon 还是先来看一下docker Deamon的一些相关知识&#xff0c;依旧是采用问答的方式来进行。为了文章的整…

设计模式_访问者模式

访问者模式 介绍 设计模式定义案例问题堆积在哪里访问模式访问模式是行为型设计模式 从对象中分类出算法 这些算法封装为对象&#xff0c; 这样这些算法类很容易扩展&#xff0c;添加新的算法类就可以了不同的VIP用户 在不同的节日 领取不同的礼物if else太多 解决办法小技巧…

专访HuggingFace CTO:开源崛起、创业故事和AI民主化丨智源独家

导读 HuggingFace CTO Julien Chaumond认为&#xff0c;在大模型时代&#xff0c;AI民主化至关重要。随着大语言模型和复杂人工智能系统的崛起&#xff0c;持续提升AI技术的可及性有助于确保这些技术的获取和控制不集中在少数强大实体手中。技术民主化促进了机会均等&#xff0…

下载安装PyCharm的步骤

1、首先进入Pycharm官网&#xff0c;并进行下载&#xff0c;日常使用社区版也是OK的 官网&#xff1a;https://www.jetbrains.com/pycharm/download/?sectionwindows 2、可以自定义路径进行安装&#xff0c;注意路径要全英哈 3、大家可以根据自己的需要来进行勾选 4、安装完成…

(免费领源码)小程序+spring boot+masql校园志愿者管理系统99213-计算机毕业设计项目选题推荐

摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;校园志愿者管理系统被用户普遍使用&#xff0c;为方便用户…

多态 虚函数表深度剖析 纯干货讲解(2)

&#x1f4af; 博客内容&#xff1a;多态 &#x1f600; 作  者&#xff1a;陈大大陈 &#x1f680; 个人简介&#xff1a;一个正在努力学技术的准C后端工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎私信&#xff01; &#x1f496; 欢迎大家&#xff1a;这里是CSD…

两种MySQL OCP认证应该如何选?

很多同学都找姚远老师说要参加MySQL OCP认证培训&#xff0c;但绝大部分同学并不知道MySQL OCP认证有两种&#xff0c;以MySQL 8.0为例。 一种是管理方向&#xff0c;叫&#xff1a;Oracle Certified Professional, MySQL 8.0 Database Administrator&#xff08;我考试的比较…

Mac安装VMware

去官网下载一下VMware Download VMware Fusion | VMware | SG 下载完成之后&#xff0c;打开直接闪退&#xff0c;参考这篇文章解决 解决macOS13安装Fusion13闪退的问题-CSDN博客 然后即可成功顺行

「视频编码软件」Media Encoder(Me) 2024 Mac/win中文版下载安装

Adobe Media Encoder(Me) 2024是一款专业的视频编码工具&#xff0c;它可以将各种视频格式进行转换、压缩和编码&#xff0c;以满足不同媒体平台和设备的需求。 以下是 Media Encoder 2023 的主要功能和新增功能&#xff1a; 视频编码和转换&#xff1a;支持将各种视频格式进…

idea文件比对

idea文件比对 1.项目内的文件比对2.项目间的文件比对3. 剪切板对比4. 版本历史(不同分支和不同commit)对比 1.项目内的文件比对 在项目中选择好需要比对的文件(类)&#xff0c;然后选择Compare Files Mac下的快捷键是Commandd&#xff0c; 这样的比对像是git冲突解决一样 …

Leetcode41缺失的第一个正数

思路&#xff1a;原地哈希表 长度为N的数组&#xff0c;没有出现过的正整数一定是1~N1中的一个。 此时会思考能不能用一个哈希表来保存出现过的1~N1的数&#xff0c;然后从 1 开始依次枚举正整数&#xff0c;并判断其是否在哈希表中 但是题目要求常数级别的空间&#xff0c;就不…

OpenGL_Learn06(纹理)

接着之前的OpenGL_Learn05&#xff08;纹理&#xff09;-CSDN博客 1. 修改片段着色器 修改片段着色器&#xff0c;仅让笑脸图案朝另一个方向看 >>>>> 纹理坐标的Y轴没有进行改变&#xff0c;需要改变的是X轴的纹理坐标 片段代码改写如下 #version 330 core out…

SpringCloud 微服务全栈体系(十一)

第十章 RabbitMQ 三、SpringAMQP SpringAMQP 是基于 RabbitMQ 封装的一套模板&#xff0c;并且还利用 SpringBoot 对其实现了自动装配&#xff0c;使用起来非常方便。 SpringAmqp 的官方地址&#xff1a;https://spring.io/projects/spring-amqp SpringAMQP 提供了三个功能&…

九、W5100S/W5500+RP2040树莓派Pico<SNTP 获取网络时间>

文章目录 1 前言2 协议简介2.1 什么是SNTP2.2 SNTP的优点2.3 SNTP原理2.4 应用场景 3 WIZnet以太网芯片4 SNTP网络设置示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 随着科技的不断进步和应用需求的不断变…