当前位置: 首页 > news >正文

Android HandlerThread、Looper、MessageQueue 源码分析

Android HandlerThread、Looper、MessageQueue 源码分析

简介

Android 开发中,大家应该对 HandlerThread 有一定了解。顾名思义,HandlerThreadThread 的一个子类。与普通的 Thread 不同,Thread 通常一次只能执行一个后台任务,如果需要执行多个任务,就必须创建多个线程,这样容易导致资源管理复杂,甚至可能出现内存泄漏等问题。而 HandlerThread 有一个显著的特点,它能够串行地执行多个任务。每个任务通过消息的形式排队执行,线程池中的任务按顺序依次处理,无需手动管理线程的生命周期或调度过程。我们只需通过 Handler 将任务发送到 HandlerThread 中,它会自动按顺序执行,极大简化了开发过程。使用 HandlerThread 的最大优势是:它是单线程的,因此不需要担心线程安全问题。实际上,Android 源码中也有许多地方使用了 HandlerThread,它在设计思想上值得我们学习和借鉴。接下来,我们将通过源码进一步了解它的实现原理。

源码分析

HandlerThread内部持有一个Looper,Looper中持有一个消息队列MessageQueue

Looper

在了解HandlerThread前,我们有必要先认识一下Looper,Looper 是一个负责管理消息队列(MessageQueue)并循环处理其中消息的类。简单来说,Looper 通过不断地从消息队列中取出消息并处理它们,来实现线程的消息处理机制。每个线程都有自己的消息队列和 Looper,通过 Looper,线程能够不断地处理任务直到任务队列为空。接下来我们会涉及到它以下几个核心接口。

  • prepare() 是一个静态方法,用来构建一个Looper对象,quitAllowed表示这个Looper是否支持退出,默认是支持,像Android 主线程的Looper是不支持退出的
    public static void prepare() {prepare(true);}
  • loopOnce 静态方法,顾名思义,循环一次,它的作用是从当前消息队列MessageQueue中取一条符合条件的消息进行分发操作
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // 取一条消息if (msg == null) {//正常没消息,队列会堵塞,不会返回null,所以这里是null肯定是已经要结束了,这里返回false 退出looperreturn false;}//分发消息代码逻辑省略}
  • loop() 静态方法,启动循环,在循环中不断调用loopOnce来处理消息
//关键代码public static void loop() {final Looper me = myLooper();
//...for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {//返回false 意味着循环结束return;}}}
  • quit()quitSafely() 退出looper,前者是立即退出,后者是处理完当前队列中所有消息后退出,最终是调用消息队列MessageQueue对应的退出方法
    public void quit() {mQueue.quit(false);
}public void quitSafely() {mQueue.quit(true);
}

HandlerThread

  • 既然它是一个线程,那可以先从run方法入手:
@Overridepublic void run() {mTid = Process.myTid();Looper.prepare();synchronized (this) {mLooper = Looper.myLooper();notifyAll();}Process.setThreadPriority(mPriority);onLooperPrepared();Looper.loop();mTid = -1;}

可以看出,线程跑起来后,先初始化了一个Looper,然后启动死循环,HandlerThread在这里充当的作用是在子线程中开启死循环接受和分发消息
这里有个地方比较有意思,在mLooper = Looper.myLooper();后面,它调用了notifyAll(),它起到了什么作用呢?

  • getLooper(),我们应该知道要把任务提交给HandlerThread执行,需要借助Handler,但是Handler的构造参数是需要传入一个Looper对象,所以,这里对外公开了获取Looper的接口
public Looper getLooper() {if (!isAlive()) {return null;}boolean wasInterrupted = false;// If the thread has been started, wait until the looper has been created.synchronized (this) {while (isAlive() && mLooper == null) {try {wait();} catch (InterruptedException e) {wasInterrupted = true;}}}/** We may need to restore the thread's interrupted flag, because it may* have been cleared above since we eat InterruptedExceptions*/if (wasInterrupted) {Thread.currentThread().interrupt();}return mLooper;}

这里是直接返回了在run中初始化的mLooper,但是呢,在非当前线程获取mLooper对象,就会引发线程安全问题,可能mLooper还没被初始化就调用了getLooper(),这样就有可能返回一个空的数据了,所以官方在这里做了while循环,并且使用了wait()堵塞,等待上面run初始化完成后再notifyAll()这里

  • getThreadHandler() 就是公开给外面向当前HandlerThread插入消息的接口,内部维护着一个Handler对象
    @NonNullpublic Handler getThreadHandler() {if (mHandler == null) {mHandler = new Handler(getLooper());}return mHandler;}
  • quit()quitSafely() 同样,HandlerThread也有退出的方法,其实现也是调用looper对应的函数退出

MessageQueue

由于关于消息分发逻辑在其他地方讲过,这里只要分析退出队列的逻辑。
它实现了退出和安全退出的方法,这两个操作有什么区别呢,请看源码

  • next() looper每调用一次loopOnce,内部就会调用messageQueue获取一条消息
Message next() {//只放关键代码for (; ; ) {//堵塞,等待消息或者时机合适或主动唤醒nativePollOnce(ptr, nextPollTimeoutMillis);//...//符合分发条件的 返回这条消息给looperif (msg != null) {//...return msg;} else {// No more messages.nextPollTimeoutMillis = -1;}//...//退出状态 则释放队列,结束循环if (mQuitting) {dispose();return null;}}
}
  • quit()实际逻辑由下面两个removeAllFutureMessagesLocked和removeAllMessagesLocked函数执行
void quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");}synchronized (this) {if (mQuitting) {//如果已经给队列设置了退出信号,下面的逻辑就不用走了return;}mQuitting = true;//标记当前队列处于退出状态,此时不再接受新的消息if (safe) {//安全退出removeAllFutureMessagesLocked();} else {//立即退出removeAllMessagesLocked();}//把队列的消息标记完成后,唤醒上面next的堵塞位置nativePollOnce,执行剩下的退出逻辑nativeWake(mPtr);}
}
//移除所有消息
private void removeAllMessagesLocked() {//mMessages 在这里是队列的头Message p = mMessages;while (p != null) {//从头开始,把所有消息标记为回收状态Message n = p.next;p.recycleUnchecked();p = n;}mMessages = null;//队头消息置空,next()执行时会判断这个,为空直接退出队列
}//移除所有未来消息,退出这一瞬间之前提交的消息保留继续执行
private void removeAllFutureMessagesLocked() {final long now = SystemClock.uptimeMillis();Message p = mMessages;if (p != null) {if (p.when > now) {//队头的时间比当前新,说明全是后面新加的,全部回收掉removeAllMessagesLocked();} else {//以下为从消息队列中找到退出那一瞬间时间一样的分割点,把分割点前的消息与队列断开链接Message n;for (;;) {n = p.next;if (n == null) {return;}if (n.when > now) {break;}p = n;}p.next = null;//断开链接,这里把队列从分割点切断do {p = n;n = p.next;p.recycleUnchecked();//把所有未来消息标记为回收状态} while (n != null);}}
}

从上面可以看出,安全退出时,队列会把所有未来消息移除掉,并且不再接受新的消息,队列中剩下的消息会继续被looper取,一直到取完为止,然后结束队列退出循环,而普通退出就会把队列中所有消息移除,然后紧接着结束队列退出循环

quit和quitSafely 应用场景有哪些呢

;//把所有未来消息标记为回收状态
} while (n != null);
}
}
}


从上面可以看出,安全退出时,队列会把所有未来消息移除掉,并且不再接受新的消息,队列中剩下的消息会继续被looper取,一直到取完为止,然后结束队列退出循环,而普通退出就会把队列中所有消息移除,然后紧接着结束队列退出循环### quit和quitSafely 应用场景有哪些呢
举一个简单的例子,在Android 使用Room操作数据库,由于操作数据库需要在子线程,所以,我们可以构造一个`HandlerThread`,专门处理操作数据库的任务,如果操作过程非常耗时,然后又要关闭数据库,

相关文章:

Android HandlerThread、Looper、MessageQueue 源码分析

Android HandlerThread、Looper、MessageQueue 源码分析 简介 在 Android 开发中,大家应该对 HandlerThread 有一定了解。顾名思义,HandlerThread 是 Thread 的一个子类。与普通的 Thread 不同,Thread 通常一次只能执行一个后台任务&#x…...

HTML知识点详解教程

文章目录 HTML知识点详解教程1. HTML基本语法2. HTML标签详解2.1 分区标签 <div>2.2 标题标签 <h1> ~ <h6>2.3 段落标签 <p>2.4 图片标签 <img>2.5 列表标签 <ul> 和 <ol>无序列表 <ul>有序列表 <ol> 2.6 超链接标签 &l…...

[数据结构#1] 并查集 | FindRoot | Union | 优化 | 应用

目录 1. 并查集原理 问题背景 名称与编号映射 数据结构设计 2. 并查集基本操作 (1) 初始化 (2) 查询根节点 (FindRoot) (3) 合并集合 (Union) (4) 集合操作总结 并查集优化 (1) 路径压缩 (2) 按秩合并 3. 并查集的应用 (1) 统计省份数量 (2) 判断等式方程是否成…...

科研绘图系列:R语言绘制网络图和密度分布图(network density plot)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载图1图2图3图4图5图6图7图8系统信息参考介绍 R语言绘制网络图和密度分布图(network & density plot) 加载R包 library(magrittr) library(dplyr) library(…...

Linux中输入和输出基本过程

1.文件内核级缓冲区 前面在如何理解Linux一切皆文件的特点中提到为了保证在Linux中所有进程访问文件时的方式趋近相 同&#xff0c;在f ile 结构体中存在一个 files_operations 结构体指针&#xff0c;对应的结构体保存所有文件操作的函 数指针&#xff08;这个结构体也被称为…...

使用 acme.sh 签发和自动续期 ssl https 证书

acme.sh 是一个热度非常高的签发和自动续期 https 证书的工具&#xff0c;虽然官网上提供了充分的操作说明&#xff0c;但是不够简洁&#xff0c;本文以在 nginx 中签发和配置http 为例&#xff0c;列出必要的几个简单步骤。 安装 因为网络原因&#xff0c;github 大部分人是…...

spring重点面试题总结

bean的生命周期 在 Spring 中&#xff0c;BeanDefinition、Bean 实例化、依赖注入、Aware 接口的处理、以及 BeanPostProcessor 的前置和后置处理等&#xff0c;都是 Spring 容器管理 Bean 生命周期的关键部分。下面我将详细解释这些过程。 1. 通过 BeanDefinition 获取 Bean…...

新的一章:codegeex

三层结构的优点&#xff1a;可扩展性&#xff0c;可复用性...

游戏引擎学习第50天

仓库: https://gitee.com/mrxiao_com/2d_game Minkowski 这个算法有点懵逼 回顾 基本上&#xff0c;现在我们所处的阶段是&#xff0c;回顾最初的代码&#xff0c;我们正在讨论我们希望在引擎中实现的所有功能。我们正在做的版本是初步的、粗略的版本&#xff0c;涵盖我们认…...

快速理解类的加载过程

当程序主动使用某个类时&#xff0c;如果该类还未加载到内存中&#xff0c;则系统会通过如下三个步骤来对该类进行初始化&#xff1a; 1.加载&#xff1a;将class文件字节码内容加载到内存中&#xff0c;并将这些静态数据转换成方法区的运行时数据结构&#xff0c;然后生成一个…...

医院跌倒检测识别 使用YOLO,COCO ,VOC格式对4806张原始图片进行标注,可识别病人跌倒,病人的危险行为,病床等场景,预测准确率可达96.7%

医院跌倒检测识别 使用YOLO,COCO ,VOC格式对4806张原始图片进行标注&#xff0c;可识别病人跌倒&#xff0c;病人的危险行为&#xff0c;病床等场景&#xff0c;预测准确率可达96.7&#xff05; 数据集分割 4806总图像数 训练组70&#xff05; 3364图片 有效集20&#…...

[Unity Shader] 【游戏开发】【图形渲染】Unity Shader的种类2-顶点/片元着色器与固定函数着色器的选择与应用

Unity 提供了不同种类的 Shader,每种 Shader 有其独特的优势和适用场景。在所有类型的 Shader 中,顶点/片元着色器(Vertex/Fragment Shader)与固定函数着色器(Fixed Function Shader)是两种重要的着色器类型。尽管它们具有不同的编写方式和用途,理解其差异与应用场景,对…...

浏览器端的 js 包括哪几个部分

一、核心语言部分 1. 变量与数据类型 变量用于存储数据&#xff0c;在 JavaScript 中有多种数据类型&#xff0c;如基本数据类型&#xff08;字符串、数字、布尔值、undefined、null&#xff09;和引用数据类型&#xff08;对象、数组、函数&#xff09;。 let name "…...

GoogLeNet网络:深度学习领域的创新之作

目录 ​编辑 引言 GoogLeNet的核心创新&#xff1a;Inception模块 Inception模块的工作原理 1x1卷积&#xff1a;降维与减少计算量 1x1卷积的优势 深度分离卷积&#xff1a;计算效率的提升 深度分离卷积的实现 全局平均池化&#xff1a;简化网络结构 全局平均池化的作…...

深入C语言文件操作:从库函数到系统调用

引言 文件操作是编程中不可或缺的一部分&#xff0c;尤其在C语言中&#xff0c;文件操作不仅是处理数据的基本手段&#xff0c;也是连接程序与外部世界的重要桥梁。C语言提供了丰富的库函数来处理文件&#xff0c;如 fopen、fclose、fread、fwrite 等。然而&#xff0c;这些库…...

Java序列化

Java序列化 简单来说&#xff1a; 序列化是将对象的状态信息转换为可以存储或传输的形式&#xff08;如字节序列&#xff09;的过程。在 Java 中&#xff0c;通过序列化可以把一个对象保存到文件、通过网络传输到其他地方或者存储到数据库等。最直接的原因就是某些场景下需要…...

基坑表面位移沉降倾斜自动化监测 非接触式一体化解决机器视觉

基于变焦视觉位移监测仪的基坑自动化监测新方案是一种集成了光学、机械、电子、边缘计算、AI识别以及云平台软件等技术的自动化系统。该方案利用变焦机器视觉原理&#xff0c;结合特殊波段成像识别技术和无源靶标&#xff0c;实现了非接触式大空间、多断面、多测点的高精度水平…...

提升效率:精通Windows命令行的艺术

文章目录 引言1. 基本目录操作命令dir&#xff1a;列出目录内容cd&#xff1a;更改目录mkdir 和 rmdir&#xff1a;创建和删除目录 2. 文件操作命令copy&#xff1a;复制文件或目录move&#xff1a;移动或重命名文件/目录del&#xff1a;删除文件 3. 文件查看命令type&#xff…...

ESP32-S3-devKitC-1 点亮板上的WS2812 RGB LED

ESP32-S3-devKitC-1 板上自带了一个RGB LED&#xff0c;型号为 WS2812。 RGB LED 在板上的位置如下图所示。 为了点亮这个WS2812&#xff0c;需要确定这颗RGB LED连接到哪个GPIO上了。 下面是确定GPIO管脚的过程&#xff1a; 1、根据原理图 2、根据PCB布局图&#xff1a; 程…...

python调用matlab函数(内置 + 自定义) —— 安装matlab.engine

文章目录 一、简介二、安装matlab.engine2.1、基于 CMD 安装2.2、基于 MATLAB 安装&#xff08;不建议&#xff09; 三、python调用matlab函数&#xff08;内置 自定义&#xff09; 一、简介 matlab.engine&#xff08;MATLAB Engine API for Python&#xff09;&#xff1a;…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...