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

【Android基础回顾】二:handler消息机制

Android 的 Handler 机制 是 Android 应用中实现线程间通信、任务调度、消息分发的核心机制之一,它基于 消息队列(MessageQueue)+ 消息循环(Looper)+ 消息处理器(Handler) 组成。

1 handler的使用场景

功能类别典型用途使用方式
线程间通信子线程通知主线程更新 UIsendMessage() / post()
延时任务倒计时、延迟执行postDelayed()
定时循环动画帧刷新、轮播图切换postDelayed(Runnable)
子线程任务管理使用 HandlerThread 串行任务调度new Handler(looper)
节流防抖防止按钮重复点击、频繁操作removeCallbacks + postDelayed
回主线程执行框架内部切线程、UI 安全调用Handler(Looper.getMainLooper())
系统/组件内部通信机制ActivityThread、HandlerThread用于 Activity 启动/管理等

1.1 线程间通信(跨线程调度任务)

主线程不能执行耗时操作(如网络/IO),子线程完成后必须通知主线程更新 UI。由于只能主线程操作 UI,Handler 就是桥梁。

new Thread(() -> {String data = getDataFromNetwork();Message msg = Message.obtain();msg.obj = data;handler.sendMessage(msg); // 主线程 handler 处理
}).start();

这种情况子线程中无 Looper,只能用主线程的 Handler 来发送消息;消息进入主线程的 MessageQueue,由主线程 Looper 分发执行。

1.2 延时执行任务

需要在一定时间后再执行某个操作,比如自动关闭提示框、执行重试等。

handler.postDelayed(() -> {// 执行延时任务
}, 3000); // 延时 3 秒

postDelayed() 本质是向 MessageQueue 中插入一个 when=now+delay 的 Message;

Looper 会按时间顺序读取,时间未到则继续等待。

1.3 循环/定时任务

用于实现定时器、动画帧刷新、轮播图等。

Runnable runnable = new Runnable() {@Overridepublic void run() {updateUI();handler.postDelayed(this, 1000); // 每 1 秒执行一次}
};
handler.post(runnable);

每次执行后重新 postDelayed 触发下一次;也可通过 handler.removeCallbacks(runnable) 停止循环。

1.4 子线程消息循环(HandlerThread)

希望在子线程中串行执行多个异步任务(如磁盘写入、数据库访问),使用 HandlerThread。

HandlerThread thread = new HandlerThread("Worker");
thread.start();
Handler workerHandler = new Handler(thread.getLooper());workerHandler.post(() -> {// 子线程中执行任务
});

HandlerThread 是自带 Looper 的子线程;

getLooper() 返回该线程的消息循环系统;

所有任务在这个线程的 MessageQueue 串行处理。

1.5 事件节流/防抖

控制按钮重复点击、频繁网络请求等行为(节流或防抖)。

handler.removeCallbacks(task);
handler.postDelayed(task, 300); // 最后一次触发后 300ms 执行

每次触发都取消上一次的 Runnable;

如果在 delay 时间内再次触发,就不断重置延迟;

适合处理频率敏感事件(如搜索框自动联想、滚动监听)。

1.6 主线程执行调度(Handler+Looper.getMainLooper())

某些非 UI 线程中又需要确保在主线程执行一段逻辑时(比如框架层调用 UI 回调)。

Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(() -> {// 保证在主线程中执行
});

使用主线程的 Looper;

保证 UI 相关代码运行在主线程,避免异常和崩溃。

1.7 框架内部通信封装(如 Retrofit/OkHttp 回调)

很多第三方库为了线程安全,使用 Handler 将回调切换回主线程。

Platform.get().callbackExecutor().execute(() -> {handler.post(() -> callback.onSuccess(result));
});

2 Handler 消息机制全流程(按时间线)

handler机制可以用于两个线程的通讯,也可以用作单线程内部使用。为了方便理解,下面以线程A和线程B为例,进行推演。
下面是一个常规操作,子线程B执行下载文件,下载完毕通知主线程A。

2.1 线程初始化Lopper

Looper.prepare(); // 为当前线程(主线程A)创建 MessageQueue 和 Looper

主线程A执行prepare, 那么MessageQueue 和 Looper在主线程A。主线程默认带有Looper,这里只是为了理解写的代码。

2.2 创建 Handler 实例(并与 Looper 绑定)

Handler mainHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// 处理消息逻辑}
};

主线程A创建一个mainHandler 。

2.3 发送消息

Message msg = handler.obtainMessage(1, "data");
mainHandler .sendMessage(msg); // 消息加入 MessageQueue

子线程B把文件下载完了,通过子线程B的

2.4 启动消息循环

Looper.loop(); // 开始无限循环,从 MessageQueue 取出消息

主线程的Looper在无线循环,拿到了子线程通过mainHandler发送过来的消息。

2.5 Looper 取出消息并调用目标 Handler 处理

mainHandler.dispatchMessage(msg); → mainHandler.handleMessage(msg)

主线程处理了子线程的消息。

2.6 完整例子

public class MainActivity extends AppCompatActivity {// 创建主线程 Handlerprivate Handler mainHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {// 接收到子线程传来的消息String result = (String) msg.obj;Log.d("MainHandler", "收到任务完成通知:" + result);// 这里可以更新 UI,比如:// textView.setText(result);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 启动子线程执行耗时任务new Thread(() -> {Log.d("WorkerThread", "开始耗时任务");// 模拟耗时操作(比如网络请求)try {Thread.sleep(2000); // 2 秒耗时} catch (InterruptedException e) {e.printStackTrace();}String result = "任务完成,结果为:42";// 向主线程发送消息Message msg = Message.obtain();msg.obj = result;mainHandler.sendMessage(msg);}).start();}
}

通常有以下两种方式传递消息,根据不同场景选取。

用法特点
sendMessage() + handleMessage()适合复杂消息传递,带参数
post(Runnable)简洁、直接执行主线程逻辑

3 handler底层数据结果的关系

在这里插入图片描述

3.1 MessageQueue 工作机制

是一个时间排序的单向链表,每个 Message 有 when 字段(何时处理)。

插入消息时按 when 排序插入。

next() 方法通过 epoll() 或 nativePollOnce() 在等待消息。

这里可以引申出epoll和nativePollOnce的概念。

3.1.1 epoll

epoll() 是 Linux 提供的 高性能 I/O 多路复用机制。

它的作用是阻塞等待文件描述符(FD)上有事件发生,比如网络数据、管道、匿名 fd、有输入等,它是 select/poll 的高效替代,适合大规模并发事件等待场景。

3.1.2 nativePollOnce

nativePollOnce() 是 Android native 层封装的 epoll 封装,在 MessageQueue 的 native 实现中(android_os_MessageQueue.cpp),会通过 Looper::pollOnce() 调用 epoll_wait() 封装函数。

//这个调用就是 在 native 层阻塞等待是否有新的消息需要处理。
int Looper::pollOnce(int timeoutMillis, ...) {return epoll_wait(epoll_fd, events, maxEvents, timeoutMillis);
}

3.1.3 为什么 Looper.loop() 是死循环但不会卡死?

for (;;) {Message msg = queue.next(); // 这一步是“阻塞等待”msg.target.dispatchMessage(msg); // 派发给 Handler
}

queue.next() 其实最终会调用到 native 的 nativePollOnce(timeout)
当没有消息可处理时,会阻塞在 epoll 上:

epoll_wait(...) // nativePollOnce 里核心操作

关键点:阻塞不是“卡死”,而是高效等待。
CPU 不做无意义运算,也不 busy-loop(不会疯狂循环空转),
而是 挂起线程直到有事件到来,这就是 epoll 的魅力。

3.1.4 事件是怎么唤醒的?为什么能收到消息?

Handler 发消息过程:

你在 Java 层调用 handler.sendMessage(),消息被插入 MessageQueue,如果 Looper 正在 epoll_wait 阻塞,它会被唤醒。

唤醒原理是,MessageQueue 中有一个 “wakeup pipe”。插入消息时,向管道写入一个字节,epoll 监听管道,立刻从阻塞中醒来,处理消息!epoll + pipe 配合,让死循环变成“事件驱动”。

在这里插入图片描述

Q&A

一个线程可以有多少个messagequeue?

不能,最多只能有一个

一个线程可以有多少个handler?

是的,可以有。

问题答案
一个线程能有多少个 Handler?没有限制,可以有很多个
它们之间共享什么?共享同一个 LooperMessageQueue
各个 Handler 的职责?每个 Handler 处理自己的消息,逻辑可以分开组织

为什么需要多个handler呢,一个是可以做到逻辑拆分,这个好理解。
另外一个是可以做到不同的延时和优先级策略,比如说:
handlerA.sendMessageDelayed(…)
handlerB.postAtTime(…)
这两个函数都会被加入同一个 MessageQueue,但根据时间排序执行。

需要注意的是。

说明
一个线程只能有一个 Looper否则 Looper.prepare() 会抛异常
Handler 必须绑定 Looper默认绑定当前线程的 Looper
线程没 Looper 时创建 Handler 会报错❌ 需先调用 Looper.prepare()

一个线程可以有多少个Looper?

不能。

假如有A和B通讯,执行A线程的handler.post(),那么逻辑在哪里执行?

调用 A线程 中的 Handler 的 post(),Runnable 中的逻辑一定会在 A 线程中执行。

在这里插入图片描述

handler.post()这个函数的执行,背后发生了什么?

handler.post(runnable) 背后会把 Runnable 包装成 Message,插入到 MessageQueue,由绑定的 Looper 线程轮询取出并调用 Runnable.run()。

在这里插入图片描述

handler发送消息到messagequeue,Looper从messagequeue取消息,取到的消息去到哪里了?

取到的消息会被传给 msg.target.dispatchMessage(msg),也就是消息所属的 Handler 处理。最终:

  • 如果是 handler.post(runnable):执行的是 runnable.run()
  • 如果是 handler.sendMessage(msg):执行的是 handler.handleMessage(msg)

所以Looper 取出的消息,会被交给它对应的 Handler 处理。

I/O 多路复用机制怎么理解?

传统阻塞式 I/O 有个问题:

每次 read() 或 recv() 调用都会 阻塞当前线程,直到数据到来。

如果你要同时监听 100 个 socket,就得起 100 个线程或写 100 次轮询代码,很低效。

I/O 多路复用就是用一个线程(或很少线程)高效处理多个连接的状态变化,避免大量线程阻塞、切换。

下面是常见的多路复用系统调用。

方法描述效率主要平台
select()最早的方式,基于 FD 数组差(每次都遍历全部)UNIX/Linux
poll()改进版,基于 FD 列表中等UNIX/Linux
epoll()高效事件驱动,使用内核数据结构Linux
kqueue类似 epoll,用于 BSD/macOSBSD/macOS
IOCPWindows 的完成端口机制Windows

epoll的工作路程大致是这样的。

  1. 注册关注的 I/O 事件(比如某 socket 可读)
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
  1. 阻塞等待事件触发(所有监听的 fd 中,哪个变了?)
epoll_wait(epfd, events, MAX_EVENTS, timeout);
  1. 事件来了后再去读/写
read(events[i].fd, buf, len);

它不会遍历全部 FD,只关注“谁发生了事件”。由内核来辅助管理事件队列,可处理成千上万个连接(用于高并发服务器)。

下面来个比较好理解的类比。

模型行为
阻塞 I/O每个顾客分一个服务员,服务员只能盯着一个人(低效)
非阻塞轮询一个服务员轮流问每个顾客“你吃好了吗?”(忙碌)
多路复用一个服务员装了对讲机,顾客吃好了主动通知他(高效)

持续更新中。。。

相关文章:

【Android基础回顾】二:handler消息机制

Android 的 Handler 机制 是 Android 应用中实现线程间通信、任务调度、消息分发的核心机制之一,它基于 消息队列(MessageQueue) 消息循环(Looper) 消息处理器(Handler) 组成。 1 handler的使用…...

每日Prompt:每天上班的状态

提示词 一个穿着清朝官服的僵尸脸上贴着符纸,在电脑面前办公,房间阴暗,电脑桌面很乱,烟灰缸里面满是烟头...

.net ORM框架dapper批量插入

.NET ORM 框架 Dapper 批量插入全解析 在 .NET 开发中,与数据库交互是常见需求。Dapper 作为轻量级的 ORM(对象关系映射)库,在简化数据库交互方面表现出色。今天我们就来深入探讨 Dapper 实现批量插入的几种方法。 为什么需要批…...

C++11 右值引用:从入门到精通

文章目录 一、引言二、左值和右值(一)概念(二)区别和判断方法 三、左值引用和右值引用(一)左值引用(二)右值引用 四、移动语义(一)概念和必要性(二…...

.net 使用MQTT订阅消息

在nuGet下载M2Mqtt V4.3.0版本。(支持.net framework) 订阅主题 public void LoadMQQCData() {string enpoint "xxx.xxx.x.x";//ip地址int port 1883;//端口string user "usrname";//用户名string pwd "pwd";//密码…...

Python实现快速排序的三种经典写法及算法解析

今天想熟悉一下python的基础写法,那就从最经典的快速排序来开始吧: 1、经典分治写法(原地排序) 时间复杂度:平均O(nlogn),最坏O(n) 空间复杂度:O(logn)递归栈空间 特点:通过左右指针…...

【递归、搜索与回溯】综合练习(四)

📝前言说明: 本专栏主要记录本人递归,搜索与回溯算法的学习以及LeetCode刷题记录,按专题划分每题主要记录:(1)本人解法 本人屎山代码;(2)优质解法 优质代码…...

强化学习入门:Gym实现CartPole随机智能体

前言 最近想开一个关于强化学习专栏,因为DeepSeek-R1很火,但本人对于LLM连门都没入。因此,只是记录一些类似的读书笔记,内容不深,大多数只是一些概念的东西,数学公式也不会太多,还望读者多多指教…...

STM32:CAN总线精髓:特性、电路、帧格式与波形分析详解

声明:此博客是我的学习笔记,所看课程是江协科技的CAN总线课程,知识点都大同小异,我仅进行总结并加上了我自己的理解,所引案例也都是课程中的案例,希望对你的理解有所帮助! 知识点1【CAN总线的概…...

贝叶斯深度学习!华科大《Nat. Commun.》发表BNN重大突破!

华科大提出基于贝叶斯深度学习的超分辨率成像,成功被Nat. Commun.收录。可以说,这是贝叶斯神经网络BNN近期最值得关注的成果之一了。另外还有AAAI 2025上的Bella新框架,计算成本降低了99.7%,也非常值得研读。 显然鉴于BNN“不确定…...

【大模型LLM学习】Flash-Attention的学习记录

【大模型LLM学习】Flash-Attention的学习记录 0. 前言1. flash-attention原理简述2. 从softmax到online softmax2.1 safe-softmax2.2 3-pass safe softmax2.3 Online softmax2.4 Flash-attention2.5 Flash-attention tiling 0. 前言 Flash Attention可以节约模型训练和推理时间…...

三、元器件的选型

前言:我们确立了题目的功能后,就可以开始元器件的选型,元器件的选型关乎到我们后面代码编写的一个难易。 一、主控的选择 主控的选择很大程度上决定我们后续使用的代码编译器,比如ESP32使用的是VScode,或者Arduino&a…...

精益数据分析(95/126):Socialight的定价转型启示——B2B商业模式的价格策略与利润优化

精益数据分析(95/126):Socialight的定价转型启示——B2B商业模式的价格策略与利润优化 在创业过程中,从B2C转向B2B不仅是商业模式的转变,更是定价策略与成本结构的全面重构。今天,我们将通过Socialight的实…...

stm32_DMA

DMA 1. 概念与基本原理 DMA,全称Direct Memory Access,即直接存储器访问。它是微控制器(MCU)、嵌入式处理器中的一个独立硬件模块,用于在无需CPU干预的情况下,在不同内存区域(包括外设寄存器和…...

物联网数据归档之数据存储方案选择分析

在上一篇文章中《物联网数据归档方案选择分析》中凯哥分析了归档设计的两种方案,并对两种方案进行了对比。这篇文章咱们就来分析分析,归档后数据应该存储在哪里?及存储方案对比。 这里就选择常用的mysql及taos数据库来存储归档后的数据吧。 你在处理设备归档表存储方案时对…...

【自动驾驶避障开发】如何让障碍物在 RViz 中‘显形’?呈现感知数据转 Polygon 全流程

【自动驾驶避障开发】如何让障碍物在 RViz 中"显形"?呈现感知数据转 Polygon 全流程 自动驾驶系统中的障碍物可视化是开发调试过程中至关重要的一环。本文将详细介绍如何将自动驾驶感知模块检测到的障碍物数据转换为RViz可显示的Polygon(多边形)形式,实现障碍物…...

【C语言】C语言经典小游戏:贪吃蛇(上)

文章目录 一、游戏背景及其功能二、Win32 API介绍1、Win32 API2、控制台程序3、定位坐标(COORD)4、获得句柄(GetStdHandle)5、获得光标属性(GetConsoleCursorInfo)1)描述光标属性(CO…...

usbutils工具的使用帮助

作为嵌入式系统开发中的常用工具,usbutils 是一套用于管理和调试USB设备的Linux命令行工具集。以下是其核心功能和使用方法的详细说明: 1. 工具组成 核心命令: lsusb:列出所有连接的USB设备及详细信息(默认安装&#…...

vue2中使用jspdf插件实现页面自定义块pdf下载

pdf下载 实现pdf下载的环境安装jspdf插件在项目中使用 实现pdf下载的环境 项目需求案例背景,点击【pdf下载】按钮,弹出pdf下载弹窗,显示需要下载四个模块的下载进度,下载完成后,关闭弹窗即可! 项目使用的是…...

如何防止服务器被用于僵尸网络(Botnet)攻击 ?

防止服务器被用于僵尸网络(Botnet)攻击是关键的网络安全措施之一。僵尸网络是黑客利用大量被感染的计算机、服务器或物联网设备来发起攻击的网络。以下是关于如何防止服务器被用于僵尸网络攻击的技术文章: 防止服务器被用于僵尸网络&#xff…...

基于cornerstone3D的dicom影像浏览器 第二十九章 自定义菜单组件

文章目录 前言一、程序结构1. 菜单数据结构2. XMenu.vue3. XSubMenu.vue4. XSubMenuSlot.vue5. XMenuItem.vue 二、调用流程总结 前言 菜单用于组织程序功能,为用户提供导航。是用户与程序交互非常重要的接口。 开源组件库像Element Plus和Ant Design中都提供了功能…...

【Block总结】DBlock,结合膨胀空间注意模块(Di-SpAM)和频域模块Gated-FFN|即插即用|CVPR2025

论文信息 标题: DarkIR: Robust Low-Light Image Restoration 作者: Daniel Feijoo, Juan C. Benito, Alvaro Garcia, Marcos Conde 论文链接:https://arxiv.org/pdf/2412.13443 GitHub链接:https://github.com/cidautai/DarkIR 创新点 DarkIR提出了…...

【学习笔记】单例类模板

【学习笔记】单例类模板 一、单例类模板 以下为一个通用的单例模式框架,这种设计允许其他类通过继承Singleton模板类来轻松实现单例模式,而无需为每个类重复编写单例实现代码。 // 命名空间(Namespace) 和 模板(Tem…...

字符串加密(华为OD)

题目描述 给你一串未加密的字符串str,通过对字符串的每一个字母进行改变来实现加密,加密方式是在每一个字母str[i]偏移特定数组元素a[i]的量,数组a前三位已经赋值:a[0]=1,a[1]=2,a[2]=4。当i>=3时,数组元素a[i]=a[i-1]+a[i-2]+a[i-3]。例如:原文 abcde 加密后 bdgkr,…...

口罩佩戴检测算法AI智能分析网关V4工厂/工业等多场景守护公共卫生安全

一、引言​ 在公共卫生安全日益受到重视的当下,口罩佩戴成为预防病毒传播、保障人员健康的重要措施。为了高效、精准地实现对人员口罩佩戴情况的监测,AI智能分析网关V4口罩检测方案应运而生。该方案依托先进的人工智能技术与强大的硬件性能,…...

Double/Debiased Machine Learning

独立同步分布的观测数据 { W i ( Y i , D i , X i ) ∣ i ∈ { 1 , . . . , n } } \{W_i(Y_i,D_i,X_i)| i\in \{1,...,n\}\} {Wi​(Yi​,Di​,Xi​)∣i∈{1,...,n}},其中 Y i Y_i Yi​表示结果变量, D i D_i Di​表示因变量, X i X_i Xi​表…...

HarmonyOS Next 弹窗系列教程(4)

HarmonyOS Next 弹窗系列教程(4) 介绍 本章主要介绍和用户点击关联更加密切的菜单控制(Menu) 和 气泡提示(Popup) 它们出现显示弹窗出现的位置都是在用户点击屏幕的位置相关 菜单控制(Menu&…...

【C】-递归

1、递归概念 递归(Recursion)是编程中一种重要的解决问题的方法,其核心思想是函数通过调用自身来解决规模更小的子问题,直到达到最小的、可以直接解决的基准情形(Base Case)。 核心:自己调用…...

飞马LiDAR500雷达数据预处理

0 引言 在使用飞马D2000无人机搭载LiDAR500进行作业完成后,需要对数据进行预处理,方便给内业人员开展点云分类等工作。在开始操作前,先了解一下使用的软硬件及整体流程。 0.1 外业测量设备 无人机:飞马D2000S激光模块&#xff…...

Kerberos面试内容整理-在 Linux/Windows 中的 Kerberos 实践

Windows 实践: 在Windows环境中,Kerberos 几乎是无形融合的。用户使用域账号登录计算机时,实际上就完成了Kerberos的AS认证并获取TGT;此后的资源访问(如共享文件夹、打印机、数据库等)都会自动使用Kerberos进行验证,而无需用户干预。Windows通过LSASS进程维护和缓存用户…...