Android HandlerThread、Looper、MessageQueue 源码分析
Android HandlerThread、Looper、MessageQueue 源码分析
简介
在 Android 开发中,大家应该对 HandlerThread 有一定了解。顾名思义,HandlerThread 是 Thread 的一个子类。与普通的 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中所有进程访问文件时的方式趋近相 同,在f ile 结构体中存在一个 files_operations 结构体指针,对应的结构体保存所有文件操作的函 数指针(这个结构体也被称为…...
使用 acme.sh 签发和自动续期 ssl https 证书
acme.sh 是一个热度非常高的签发和自动续期 https 证书的工具,虽然官网上提供了充分的操作说明,但是不够简洁,本文以在 nginx 中签发和配置http 为例,列出必要的几个简单步骤。 安装 因为网络原因,github 大部分人是…...
spring重点面试题总结
bean的生命周期 在 Spring 中,BeanDefinition、Bean 实例化、依赖注入、Aware 接口的处理、以及 BeanPostProcessor 的前置和后置处理等,都是 Spring 容器管理 Bean 生命周期的关键部分。下面我将详细解释这些过程。 1. 通过 BeanDefinition 获取 Bean…...
新的一章:codegeex
三层结构的优点:可扩展性,可复用性...
游戏引擎学习第50天
仓库: https://gitee.com/mrxiao_com/2d_game Minkowski 这个算法有点懵逼 回顾 基本上,现在我们所处的阶段是,回顾最初的代码,我们正在讨论我们希望在引擎中实现的所有功能。我们正在做的版本是初步的、粗略的版本,涵盖我们认…...
快速理解类的加载过程
当程序主动使用某个类时,如果该类还未加载到内存中,则系统会通过如下三个步骤来对该类进行初始化: 1.加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个…...
医院跌倒检测识别 使用YOLO,COCO ,VOC格式对4806张原始图片进行标注,可识别病人跌倒,病人的危险行为,病床等场景,预测准确率可达96.7%
医院跌倒检测识别 使用YOLO,COCO ,VOC格式对4806张原始图片进行标注,可识别病人跌倒,病人的危险行为,病床等场景,预测准确率可达96.7% 数据集分割 4806总图像数 训练组70% 3364图片 有效集20&#…...
[Unity Shader] 【游戏开发】【图形渲染】Unity Shader的种类2-顶点/片元着色器与固定函数着色器的选择与应用
Unity 提供了不同种类的 Shader,每种 Shader 有其独特的优势和适用场景。在所有类型的 Shader 中,顶点/片元着色器(Vertex/Fragment Shader)与固定函数着色器(Fixed Function Shader)是两种重要的着色器类型。尽管它们具有不同的编写方式和用途,理解其差异与应用场景,对…...
浏览器端的 js 包括哪几个部分
一、核心语言部分 1. 变量与数据类型 变量用于存储数据,在 JavaScript 中有多种数据类型,如基本数据类型(字符串、数字、布尔值、undefined、null)和引用数据类型(对象、数组、函数)。 let name "…...
GoogLeNet网络:深度学习领域的创新之作
目录 编辑 引言 GoogLeNet的核心创新:Inception模块 Inception模块的工作原理 1x1卷积:降维与减少计算量 1x1卷积的优势 深度分离卷积:计算效率的提升 深度分离卷积的实现 全局平均池化:简化网络结构 全局平均池化的作…...
深入C语言文件操作:从库函数到系统调用
引言 文件操作是编程中不可或缺的一部分,尤其在C语言中,文件操作不仅是处理数据的基本手段,也是连接程序与外部世界的重要桥梁。C语言提供了丰富的库函数来处理文件,如 fopen、fclose、fread、fwrite 等。然而,这些库…...
Java序列化
Java序列化 简单来说: 序列化是将对象的状态信息转换为可以存储或传输的形式(如字节序列)的过程。在 Java 中,通过序列化可以把一个对象保存到文件、通过网络传输到其他地方或者存储到数据库等。最直接的原因就是某些场景下需要…...
基坑表面位移沉降倾斜自动化监测 非接触式一体化解决机器视觉
基于变焦视觉位移监测仪的基坑自动化监测新方案是一种集成了光学、机械、电子、边缘计算、AI识别以及云平台软件等技术的自动化系统。该方案利用变焦机器视觉原理,结合特殊波段成像识别技术和无源靶标,实现了非接触式大空间、多断面、多测点的高精度水平…...
提升效率:精通Windows命令行的艺术
文章目录 引言1. 基本目录操作命令dir:列出目录内容cd:更改目录mkdir 和 rmdir:创建和删除目录 2. 文件操作命令copy:复制文件或目录move:移动或重命名文件/目录del:删除文件 3. 文件查看命令typeÿ…...
ESP32-S3-devKitC-1 点亮板上的WS2812 RGB LED
ESP32-S3-devKitC-1 板上自带了一个RGB LED,型号为 WS2812。 RGB LED 在板上的位置如下图所示。 为了点亮这个WS2812,需要确定这颗RGB LED连接到哪个GPIO上了。 下面是确定GPIO管脚的过程: 1、根据原理图 2、根据PCB布局图: 程…...
python调用matlab函数(内置 + 自定义) —— 安装matlab.engine
文章目录 一、简介二、安装matlab.engine2.1、基于 CMD 安装2.2、基于 MATLAB 安装(不建议) 三、python调用matlab函数(内置 自定义) 一、简介 matlab.engine(MATLAB Engine API for Python):…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...
pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...
学习一下用鸿蒙DevEco Studio HarmonyOS5实现百度地图
在鸿蒙(HarmonyOS5)中集成百度地图,可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API,可以构建跨设备的定位、导航和地图展示功能。 1. 鸿蒙环境准备 开发工具:下载安装 De…...
