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

Android Handler 消息循环机制

核心角色先登场

首先,你要认识这个机制里的几个核心演员:

  1. Handler(处理者): 你通常直接打交道的对象。负责发送消息 (sendMessage) 和处理消息 (handleMessage)
  2. Message(消息): 一个包含描述和任意数据对象的容器。它的 what 字段是标识,obj 字段可以携带数据
  3. MessageQueue(消息队列): 一个单向链表,用来按时间顺序存储所有待处理的 Message。关键特性:它内部是空的(没有消息)或到达下一个消息的执行时间时,会使线程进入休眠状态以节省CPU资源
  4. Looper(循环器): 整个机制的心脏。它在一个线程内死循环,不断地从 MessageQueue 中取出 Message,并分发给对应的 Handler 进行处理

一个生动的比喻:快递系统

可以把它比作一个公司的快递系统

  • 线程(Thread)
    ○ → 整个公司
  • Looper
    ○ 
    → 公司里 24 小时不停业的、只有一个员工的快递分发中心。这个员工的工作就是不停地查看有没有新包裹
  • MessageQueue
    ○ 
    → 快递分发中心里的、带有自动定时功能的传送带(队列)。包裹按预计送达时间排序。如果传送带是空的,或者下一个包裹还没到预定时间,传送带就暂停(线程休眠)
  • Handler
    ○ 
    → 公司里的各个部门(如市场部、财务部)。每个部门都有一个专属的收件地址
  • Message
    ○ 
    → 包裹。包裹上必须写明收件地址(target,即哪个 Handler)和里面是什么(whatobj

工作机制详解(从Java到Native)

现在,我们跟着流程,看看这套系统是如何协同工作的

第 1 步:创建 Looper 和 MessageQueue(准备快递中心)

一个普通的线程默认是没有开启消息循环的(它的 Looper 和 MessageQueue 都是 null)。你需要手动调用 Looper.prepare() 和 Looper.loop() 来为它装备上这套机制

// 在一个子线程中
class WorkThread extends Thread {public Handler mHandler;@Overridepublic void run() {// 1. 调用 prepare()Looper.prepare(); // 2. 创建Handler,它会自动绑定到当前线程的LoopermHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// 在这里处理消息}};// 3. 启动循环,开始处理消息Looper.loop();}
}
  • Looper.prepare() (Java层):
    ○ 检查当前线程是否已经有一个 Looper 了(一个线程只能有一个 Looper)
    ○ 如果没有,就创建一个新的 Looper 对象
    ○ 在创建 Looper 的构造函数里,会创建一个至关重要的 MessageQueue 对象 (new MessageQueue())
    ○ 这个 Looper 对象会被存储到线程的局部变量 (ThreadLocal) 中,这样每个线程都能访问到自己独有的 Looper
  • new MessageQueue() (Java层):
    ○ 它的构造函数做了一件关键的事:调用一个 Native 方法 nativeInit()
    ○ 这里,我们从 Java 层进入了 Native 层 (C++/C)
  • nativeInit() (Native层):
    ○ 
    在 Native 层,同样会创建一个 NativeMessageQueue 对象
    ○ 这个 NativeMessageQueue 也会创建一个核心中的核心—— Looper (Native Looper)
    ○ 这个 Native Looper 利用了 Linux 系统的 epoll 机制。epoll 是一种高效的 I/O 事件通知机制,它可以监控多个文件描述符(fd)上的事件
    ○ 在这里,Android 创建了一个事件管道 (Event FD),包括一个“读端”和一个“写端”。这个管道就是用来实现线程休眠和唤醒
    ○ Native 的 Looper 会监控这个管道的读端。当没有消息或消息还没到执行时间时,线程就会在 epoll_wait() 调用上进入休眠状态

至此,你的线程(公司)已经拥有了一个功能完备的“快递中心”(Looper + MessageQueue),并且中心里的“传送带”(MessageQueue)已经通过一根特殊的“能源管”(Event FD)连接到了Native层的“总控开关”(epoll)上

第 2 步:发送消息(各部门寄送包裹)
// 在任何地方,比如UI线程
Message msg = Message.obtain();
msg.what = 100;
msg.obj = "Some data";
workThread.mHandler.sendMessage(msg);
  • handler.sendMessage(msg) 最终会调用到 MessageQueue.enqueueMessage(msg, uptimeMillis)
  • 这个方法会将消息按照执行时间(when)的顺序插入到消息队列(链表)的正确位置
  • 在插入过程中,如果发现队列之前是空的,或者新插入的消息是下一个要执行的“队头”消息,并且队列当前正处于休眠状态,那么它就需要唤醒队列
  • 如何唤醒? 它调用一个 Native 方法 nativeWake(mPtr)
  • nativeWake() (Native层):
    ○ 
    这个方法拿到与 Java MessageQueue 对应的 NativeMessageQueue
    ○ 然后通过 Native Looper,向我们在第 1 步创建的那个“事件管道”的写端写入一个数据
    ○ 这个写入操作会立即触发 epoll 监控到事件,导致正在 epoll_wait() 上休眠的线程被唤醒,继续工作
第 3 步:循环处理消息(快递中心开始工作)
  • Looper.loop() (Java层):
    ○ 
    这是一个死循环 for (;;)
    ○ 在循环内部,它调用 MessageQueue.next() 来获取下一条要处理的消息
  • MessageQueue.next() (Java层):
    ○ 
    这个方法内部也是一个循环
    ○ 它首先检查是否有消息到达了执行时间
    ○ 如果没有消息或消息没到期:它会调用 Native 方法 nativePollOnce(ptr, timeoutMillis),让线程进入定时休眠
  • nativePollOnce() (Native层):
    ○ 
    这个方法最终会调用到 Native Looper 的 pollOnce()
    ○ pollOnce() 内部又调用 pollInner(),而 pollInner() 的核心就是 epoll_wait() 系统调用
    ○ 线程就是在这里休眠的。它的休眠时间由下一个消息的执行时间决定(timeout)。如果在休眠期间,有新的消息被 enqueueMessage(比如一个需要立即执行的消息),nativeWake 会写入数据,epoll_wait() 会立即返回,线程被唤醒
    ○ 如果休眠超时(即到了下一个消息该执行的时间),epoll_wait() 也会返回
  • 取消息与分发:
    ○ 
    一旦 next() 方法返回了一个 Message(无论是被唤醒还是超时),Looper.loop() 就会拿到这个消息
    ○ 然后调用 msg.target.dispatchMessage(msg)。这里的 msg.target 就是发送这个消息的 Handler
    ○ 最终,消息会一路被传递到你重写的 handleMessage() 方法中,得到处理
  • 回收消息:
    ○ 
    处理完后,loop() 会调用 msg.recycleUnchecked(),将消息放回全局的消息池中,实现复用,避免频繁创建对象造成GC

总结与流程图

核心思想:利用 Linux 的 epoll 机制,在无事可做时让线程进入休眠,在有新消息时能及时唤醒,从而兼顾了响应效率和资源消耗


文章转载自:

http://9vWoBFg6.nfcxq.cn
http://0ug3rZx6.nfcxq.cn
http://iJBleUMO.nfcxq.cn
http://MXOO5wqj.nfcxq.cn
http://IsESBhig.nfcxq.cn
http://Gtcctoav.nfcxq.cn
http://U30aZKVo.nfcxq.cn
http://khVPZmVd.nfcxq.cn
http://BT7h8YZ6.nfcxq.cn
http://TdeZZAcr.nfcxq.cn
http://I2nRalLM.nfcxq.cn
http://ItPb9IYA.nfcxq.cn
http://ER2GJ2ps.nfcxq.cn
http://0o8mhRWA.nfcxq.cn
http://xPfraZVP.nfcxq.cn
http://gvukU8Dx.nfcxq.cn
http://TcN3VBxb.nfcxq.cn
http://vYn0yXey.nfcxq.cn
http://ZcYYK7pf.nfcxq.cn
http://tgvE0R26.nfcxq.cn
http://68loOu4y.nfcxq.cn
http://BcyQCrtI.nfcxq.cn
http://pxqcaBD8.nfcxq.cn
http://BjlxaXbO.nfcxq.cn
http://3I0RM5sA.nfcxq.cn
http://lWtpaaTR.nfcxq.cn
http://YnxQgk1G.nfcxq.cn
http://u3L6Z3Qd.nfcxq.cn
http://KMqtNbpA.nfcxq.cn
http://k9MDGsCf.nfcxq.cn
http://www.dtcms.com/a/365106.html

相关文章:

  • Python基础(⑨Celery 分布式任务队列)
  • 【计算机科学与应用】基于FME的自动化数据库建设方法及应用实践
  • 产线自动化效率上不去?打破设备和平台的“数据孤岛”是关键!
  • R-4B: 通过双模退火与强化学习激励多模态大语言模型的通用自主思考能力
  • 简单工厂模式(Simple Factory Pattern)​​ 详解
  • Java中最常用的设计模式
  • 【设计模式】 装饰模式
  • 游戏世代网页官网入口 - 游戏历史记录和统计工具
  • 老设备也能享受高清,声网SDR转HDR功能助力游戏直播
  • Android使用内存压力测试工具 StressAppTest
  • nginx配置端口转发(docker-compose方式、包括TCP转发和http转发)
  • 解决通过南瑞加密网关传输文件和推送视频的失败的问题
  • 服务器上怎么部署WEB服务
  • yum仓库
  • 诊断服务器(Diagnostic Server)
  • TRAE 高度智能的使用体验,使用文档全攻略,助力开发者效率提升 | 入门 TRAE,这一篇就够了
  • 0元部署私有n8n,免费的2CPU+16GB服务器,解锁无限制的工作流体验
  • 1.Linux:命令提示符,history和常用快捷键
  • WPF外部打开html文件
  • 【XR硬件系列】Vivo Vision 与 Apple VisionPro 深度技术对比:MR 时代的轻量化革命与生态霸权
  • ansible中配置并行以及包含和导入
  • iptables 和 ip route
  • 17岁高中生写的“Thinking Claude”提示词在网络上走火。提示词全文,并附高价值解读。
  • GEO优化专家孟庆涛:优质内容是GEO优化的核心
  • 使用sudo命令执行程序不保留父进程
  • 51单片机(按键,外部中断,定时器中断,PWM与蜂鸣器)
  • 【序列晋升】27 Spring Cloud Sleuth给分布式系统装上透视镜
  • Shell 秘典(卷八)—— 万流归宗秘术・AWK 通玄真解
  • GitHub热门AI编程工具推荐:CodeGeeX4+CodeLlama实战教程,程序员高效开发必备
  • etcd的强一致性和redis的最终一致性都是如何实现的?