Android 的 Handler 机制是跨线程通信异步消息处理的核心框架,它构成了 Android 应用响应性和事件驱动模型的基础(如 UI 更新、后台任务协调)。其核心思想是 “消息队列 + 循环处理”

核心组件及其关系

  1. Handler (处理器):

    • 角色: 消息的发送者处理者
    • 功能:
      • 发送消息:MessageRunnable 对象放入与其关联的 MessageQueue 中。方法包括 sendMessage(), post(), sendMessageDelayed(), postDelayed() 等。
      • 处理消息: 实现 handleMessage(Message msg) 方法,定义当消息从队列中被取出时如何执行相应的操作。对于 post(Runnable r),则是在关联线程中执行 r.run()
  2. Message (消息):

    • 角色: 通信的载体
    • 内容: 包含需要传递的数据 (what, arg1, arg2, obj, data Bundle) 和目标 Handler (target)。
    • 优化: 通常通过 Message.obtain()Handler.obtainMessage() 从对象池中获取,避免频繁创建对象引起 GC。
  3. MessageQueue (消息队列):

    • 角色: 一个按执行时间排序(主要是 when 字段)的优先级队列
    • 功能:
      • 存储消息: 接收 Handler 发送来的 Message,并根据 when (绝对时间戳) 插入到队列的合适位置。
      • 取出消息: 由关联的 Looper 循环调用 next() 方法取出下一个待处理的消息。如果队列为空或下一个消息的执行时间未到,next() 会阻塞。
    • 关键特性: 单线程 操作(每个 Looper 线程有且只有一个 MessageQueue)。
  4. Looper (循环器):

    • 角色: 消息循环的引擎。负责驱动整个消息处理流程。
    • 功能:
      • 准备循环: 通过 Looper.prepare() 为当前线程创建一个 Looper(及其关联的 MessageQueue)。
      • 启动循环: 调用 Looper.loop() 启动一个无限循环。在这个循环中:
        1. 调用 MessageQueue.next() 获取下一条消息(可能阻塞)。
        2. 如果取到 null(通常表示调用了 quit),退出循环。
        3. 否则,调用 msg.target.dispatchMessage(msg),将消息分发给发送它的 Handler 处理。
      • 结束循环: 调用 Looper.quit()(立即退出)或 Looper.quitSafely()(处理完已有消息后退出)。
    • 关键特性:
      • 单线程 操作(每个 Looper 线程有且只有一个 Looper)。
      • 主线程 (ActivityThread) 的 Looper 在应用启动时由系统自动创建并启动 (Looper.prepareMainLooper(), Looper.loop())。
      • 其他线程需要手动调用 prepare()loop() 来创建和使用 Looper(如 HandlerThread)。
  5. ThreadLocal (线程局部存储):

    • 角色: 确保每个线程访问到它自己独有的 LooperMessageQueue 实例。
    • 原理: Looper 内部使用 ThreadLocal 存储当前线程的 Looper 对象。myLooper()getMainLooper() 都依赖于它。

工作流程详解

  1. 初始化 (通常在主线程或自定义线程):

    • 主线程: 系统自动完成 Looper.prepareMainLooper()Looper.loop()
    • 自定义线程:
      class WorkerThread extends Thread {public Handler mHandler;public void run() {Looper.prepare(); // 1. 为当前线程创建 Looper 和 MessageQueuemHandler = new Handler() { // 2. 创建 Handler,自动绑定到当前线程的 Looper@Overridepublic void handleMessage(Message msg) {// 处理来自其他线程的消息}};Looper.loop(); // 3. 启动消息循环 (阻塞在此处)}
      }
      
  2. 发送消息 (例如从后台线程发往主线程更新 UI):

    // 假设在主线程初始化时保存了主线程 Handler: mainHandler
    new Thread(new Runnable() {@Overridepublic void run() {// ... 后台工作 ...Message msg = mainHandler.obtainMessage(MSG_UPDATE_UI, data);mainHandler.sendMessage(msg); // 或 mainHandler.post(updateUiRunnable)// 消息被放入主线程的 MessageQueue}
    }).start();
    
  3. 消息循环处理 (Looper.loop()):

    • 在拥有 Looper 的线程中,loop() 方法无限循环:
      public static void loop() {final Looper me = myLooper(); // 获取当前线程的 Looperfinal MessageQueue queue = me.mQueue; // 获取关联的 MessageQueuefor (;;) {Message msg = queue.next(); // 1. 取消息 (可能阻塞)if (msg == null) { // 2. 收到退出信号 (null),退出循环return;}try {msg.target.dispatchMessage(msg); // 3. 分发消息给 Handler 处理} finally {msg.recycleUnchecked(); // 4. 回收消息到对象池}}
      }
      
  4. 消息分发 (Handler.dispatchMessage(Message msg)):

    public void dispatchMessage(Message msg) {if (msg.callback != null) { // 1. 优先处理 Message 自带的 Runnable (post(Runnable r))handleCallback(msg);} else {if (mCallback != null) { // 2. 其次处理 Handler 构造时传入的 Callbackif (mCallback.handleMessage(msg)) {return;}}handleMessage(msg); // 3. 最后调用 Handler 子类实现的 handleMessage()}
    }
    

底层原理与关键技术点

  1. MessageQueue.next() 的阻塞与唤醒 (Linux epoll):

    • 问题: 当队列为空或下一个消息的执行时间 (when) 还没到时,next() 需要高效地阻塞线程,避免无意义的 CPU 空转。
    • 解决方案: 利用 Linux 的 epoll 系统调用 实现高效的 I/O 多路复用等待。
      • MessageQueue 内部有一个 native 层的对象 (mPtr 指向 NativeMessageQueue),它封装了一个 epoll 实例 (mEpollFd)。
      • 当需要阻塞时 (next() 发现没有即时消息),会调用 nativePollOnce(ptr, timeoutMillis)。这个 native 方法最终会进入 epoll_wait() 系统调用,让线程在指定的文件描述符 (event fd) 上等待。
      • 唤醒机制:
        • 当有新消息加入队列(特别是插入到队列头部时)或设置了新的屏障时,会调用 nativeWake(ptr)
        • nativeWake() 会向 event fd 写入数据
        • 这个写操作会唤醒正在 epoll_wait() 上阻塞的线程,使其返回并继续处理消息队列。
      • 延迟消息: 如果阻塞是因为等待延迟消息 (when > now),timeoutMillis 会被设置为剩余等待时间。epoll_wait() 会在超时或唤醒时返回。
  2. ThreadLocal 保证线程隔离:

    • Looper 类内部有一个 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
    • Looper.prepare() 调用 sThreadLocal.set(new Looper(quitAllowed))
    • Looper.myLooper() 调用 sThreadLocal.get()
    • 这确保了每个线程调用 myLooper() 得到的都是自己线程创建的 Looper 对象,从而每个线程都有自己独立的 MessageQueue 和消息循环。
  3. 主线程的特殊性:

    • ActivityThreadmain() 方法是 Android 应用的入口点。
    • main() 中,系统会调用 Looper.prepareMainLooper() (内部也是 prepare(false)) 创建主线程 Looper,并将其标记为“主 Looper”(set()sMainLooper 并标记 mIsMain)。
    • 然后调用 Looper.loop() 启动主线程的消息循环。所有 UI 事件(触摸、绘制、生命周期回调)以及通过主线程 Handler 发送的消息,都在这个循环中被处理。 这就是为什么在非 UI 线程直接更新 UI 会报错 - UI 操作必须在拥有 View 树的线程(通常是主线程)执行。
  4. 屏障消息 (SyncBarrier):

    • 目的: 允许异步消息优先于同步消息执行。
    • 实现:
      • 调用 MessageQueue.postSyncBarrier() 会插入一个 targetnull 的特殊 Message (屏障)。
      • next() 方法中,如果遇到屏障,它会跳过所有后续的同步消息 (msg.isAsynchronous() == false),只查找异步消息 (msg.isAsynchronous() == true) 或新的屏障。
      • 当需要移除屏障时,调用 MessageQueue.removeSyncBarrier(token)
    • 应用场景: View 系统在请求布局 (requestLayout()) 和绘制 (draw()) 时使用屏障,确保 UI 渲染相关的异步消息(如 VSYNC 信号触发的绘制)能优先执行,避免被耗时的同步消息阻塞造成卡顿。
  5. 异步消息 (Message.setAsynchronous(true)):

    • 标记为异步的消息,在遇到同步屏障时可以被优先处理。
    • 通常由系统内部使用(如 Choreographer 用于 VSYNC 回调)。开发者也可以通过 Handler 的特定构造函数 (Handler(looper, callback, async)) 或 Message.setAsynchronous(true) 发送异步消息。
  6. 对象池 (Message):

    • 为了避免频繁创建和销毁 Message 对象带来的 GC 开销,Message 内部维护了一个静态对象池 (链表结构)。
    • Message.obtain() 会从池中取出一个复用对象(如果可用),否则才新建。
    • Message.recycle()recycleUnchecked() (在 Looper.loop()finally 块中调用) 会将处理完的消息放回池中。
    • HandlerobtainMessage() 系列方法内部也是调用 Message.obtain()
  7. HandlerThread (便捷类):

    • 一个已经封装好了 LooperThread 子类。使用它创建后台线程处理消息非常方便:
      HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
      handlerThread.start(); // 内部会调用 prepare() 和 loop()
      Handler handler = new Handler(handlerThread.getLooper());
      handler.post(...); // 任务会在 handlerThread 这个后台线程执行
      

重要注意事项

  1. 内存泄漏:

    • 典型场景:Activity 中声明一个非静态内部类的 Handler。这个 Handler 隐式持有外部 Activity 的引用。
    • 问题: 如果 Handler 的消息队列中还有未处理完的消息(尤其是延迟消息),这些消息持有 Handler 引用 -> Handler 持有 Activity 引用 -> 导致 Activity 无法被 GC 回收,即使它已经被销毁。
    • 解决方案:
      • 使用 static 内部类 + WeakReference 持有 Activity
      • ActivityonDestroy() 中调用 handler.removeCallbacksAndMessages(null) 清除队列中所有该 Handler 的消息。
  2. ANR (Application Not Responding):

    • 原因: 主线程的 Looperloop() 中处理消息 (dispatchMessageRunnable.run())。如果某个消息的处理耗时过长(超过 5 秒),会阻塞后续所有消息的处理,包括屏幕绘制和用户输入事件,系统就会弹出 ANR 对话框。
    • 避免: 严禁在主线程执行耗时操作(网络请求、大文件读写、复杂计算等)。耗时操作务必放在工作线程,完成后通过 Handler 通知主线程更新 UI。

总结

Android 的 Handler 机制是一个基于生产者-消费者模型构建的、围绕消息队列事件循环的异步通信框架。Handler 负责发送消息,Message 是数据载体,MessageQueue 是存储和调度消息的优先级队列(底层依赖 epoll 实现高效阻塞/唤醒),Looper 是驱动消息循环的核心引擎(依赖 ThreadLocal 保证线程隔离)。主线程的消息循环由系统自动创建,是 UI 更新的生命线。理解其原理对于编写高效、响应流畅的 Android 应用至关重要,同时也要警惕内存泄漏和 ANR 问题。屏障消息和异步消息是系统优化 UI 性能的关键机制。HandlerThread 为后台线程使用 Handler 提供了便捷方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/88090.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/88090.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/88090.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jQuery JSONP:实现跨域数据交互的利器

jQuery JSONP&#xff1a;实现跨域数据交互的利器 引言 随着互联网的发展&#xff0c;跨域数据交互的需求日益增加。在Web开发中&#xff0c;由于同源策略的限制&#xff0c;直接通过XMLHttpRequest请求跨域数据会遇到诸多问题。而JSONP&#xff08;JSON with Padding&#xff…

Redis集群和 zookeeper 实现分布式锁的优势和劣势

在分布式系统中&#xff0c;实现分布式锁是确保多个节点间互斥访问共享资源的一种常见需求。Redis 集群 和 zookeeper 都可以用来实现这一功能&#xff0c;但它们有着各自不同的优势和劣势。 CAP 理论&#xff1a; 在设计一个分布式系统时&#xff0c;一致性&#xff08;Consis…

如何备份vivo手机中的联系人?

随着vivo移动设备在全球设立7个研发中心&#xff0c;vivo正在进入更多的国家。如今&#xff0c;越来越多的人开始使用vivo手机。以vivo X100为例&#xff0c;它配备了主摄像头和多个辅助摄像头&#xff0c;提供多样化的拍摄选项&#xff0c;并搭载了最新的FunTouch OS&#xff…

python脚本编程:使用BeautifulSoup爬虫库获取热门单机游戏排行榜

BeautifulSoup是一个便捷的解析html页面元素的python库&#xff0c;此处用来写一个简单的爬虫批量抓取国内游戏资讯网站的近期热门单机游戏排行榜。 网页来源如下所示代码 from bs4 import BeautifulSoup import requests# get web page web_url "https://www.3dmgame.co…

C#配置全面详解:从传统方式到现代配置系统

C#配置全面详解&#xff1a;从传统方式到现代配置系统 在软件开发中&#xff0c;配置是指应用程序运行时可调整的参数集合&#xff0c;如数据库连接字符串、API 地址、日志级别等。将这些参数从代码中分离出来&#xff0c;便于在不修改代码的情况下调整应用行为。C# 提供了多种…

数据中台架构解析:湖仓一体的实战设计

目录 一、数据中台与湖仓一体架构是什么 1. 数据中台 2. 湖仓一体架构 3. 湖仓一体在数据中台里的价值 二、湖仓一体架构的核心部件 1. 数据湖 2. 数据仓库 3. 数据集成工具 4. 数据分析与处理引擎 三、湖仓一体架构实战设计 1. 需求分析与规划 2. 数据湖建设 3. …

SQL Server表分区技术详解

表分区概述 表分区是将大型数据库表物理分割为多个较小单元的技术,逻辑上仍表现为单一实体。该技术通过水平分割数据显著提升查询性能,尤其针对TB级数据表可降低90%的响应时间。典型应用场景包含订单历史表、日志记录表等具有明显时间特征的业务数据,以及需要定期归档的审计…

WHIP(WebRTC HTTP Ingestion Protocol)详解

WHIP&#xff08;WebRTC HTTP Ingestion Protocol&#xff09;详解 WHIP&#xff08;WebRTC HTTP Ingestion Protocol&#xff09;是一种基于 HTTP 的协议&#xff0c;用于将 WebRTC 媒体流推送到媒体服务器&#xff08;如 SRS、Janus、LiveKit&#xff09;。它是为简化 WebRT…

图像噪点消除:用 OpenCV 实现多种滤波方法

在图像处理中&#xff0c;噪点是一个常见的问题。它可能是由于图像采集设备的缺陷、传输过程中的干扰&#xff0c;或者是光照条件不佳引起的。噪点会影响图像的质量和后续处理的效果&#xff0c;因此消除噪点是图像预处理的重要步骤之一。本文将介绍如何使用 OpenCV 实现几种常…

AI的Prompt提示词:英文写好还是中文好?

在与AI人大模型交互时,Prompt(提示词)的质量直接决定了输出的精准度和有效性。一个常见的问题是:究竟是用英文写Prompt好,还是用中文写更好?这并非一个简单的二元选择,而是涉及到语言模型的底层逻辑、表达的精确性以及个人使用习惯的综合考量。 英文Prompt的优势 模型训…

react的条件渲染【简约风5min】

const flag1true; console.log(flag1&&hello); console.log(flag1||hello); const flag20; console.log(flag2&&hello); console.log(flag2||hello); // &&运算符&#xff0c;如果第一个条件为假&#xff0c;则返回第一个条件&#xff0c;否则返回第二…

【RK3568+PG2L50H开发板实验例程】FPGA部分 | 紫光同创 IP core 的使用及添加

本原创文章由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处&#xff08;www.meyesemi.com)1.实验简介实验目的&#xff1a;了解 PDS 软件如何安装 IP、使用 IP 以及查看 IP 手册实验环境&#xff1a;Window11 PD…

thinkphp微信小程序一键获取手机号登陆(解密数据)

微信小程序获取手机号登录的步骤相对较为简单,主要分为几个部分: 1.用户授权获取手机号: 微信小程序通过调用 wx.getPhoneNumber API 获取用户授权后,获取手机号。 2.前端获取用户的手机号: 用户在小程序中点击获取手机号时,系统会弹出授权框,用户同意后,你可以通过 …

数据库设计精要:完整性和范式理论

文章目录数据的完整性实体的完整性主键域完整性参照完整性外键多表设计/多表理论一对一和一对多多对多数据库的设计范式第一范式&#xff1a;原子性第二范式&#xff1a;唯一性第三范式&#xff1a;不冗余性数据的完整性 实体的完整性 加主键&#xff0c;保证一个表中每一条数…

智能推荐社交分享小程序(websocket即时通讯、协同过滤算法、时间衰减因子模型、热度得分算法)

&#x1f388;系统亮点&#xff1a;websocket即时通讯、协同过滤算法、时间衰减因子模型、热度得分算法&#xff1b;一.系统开发工具与环境搭建1.系统设计开发工具后端使用Java编程语言的Spring boot框架项目架构&#xff1a;B/S架构运行环境&#xff1a;win10/win11、jdk17小程…

部署NextCloud AIO + Frp + nginx-proxy-manager内网穿透私有云服务

网络拓扑 假设已有域名为nextcloud.yourhost.com 用户通过域名https访问 -> Nginx -> frps -> frpc -> NextCloud 其中Nginx和frps安装在具有公网IP的服务器上&#xff0c;frpc和NextCloud安装在内网服务器中。 Nginx配置 通过docker安装nginx-proxy-manager 外…

【源力觉醒 创作者计划】文心开源大模型ERNIE-4.5-0.3B-Paddle私有化部署保姆级教程及技术架构探索

一起来轻松玩转文心大模型吧&#x1f449;一文心大模型免费下载地址: https://ai.gitcode.com/theme/1939325484087291906 前言 2025年6月30日&#xff0c;百度正式开源文心大模型4.5系列&#xff08;ERNIE 4.5&#xff09;&#xff0c;涵盖10款不同参数规模的模型&#xff0…

大模型面试:如何解决幻觉问题

在大模型面试中回答“如何解决幻觉”问题时&#xff0c;需要展现你对问题本质的理解、技术方案的掌握以及工程实践的洞察。以下是一个结构化的回答框架和关键点&#xff0c;供你参考&#xff1a;回答框架&#xff1a;问题理解 -> 解决方案 -> 总结 1. 明确问题&#xff0…

matlab实现五自由度机械臂阻抗控制下的力跟踪

五自由度机械臂阻抗控制下的力跟踪,可以实现对力的跟踪反馈&#xff0c;基于MATLAB的机器人工具箱 eyebot.m , 767 zuakang_wailiraodong.m , 2568 colormp.mat , 682

excel日志表介绍

在Excel中制作“日志表事物”&#xff08;可理解为记录事务的日志表格&#xff09;&#xff0c;通常用于系统性追踪事件、任务、操作或数据变化。以下从表格设计、核心要素、制作步骤、函数应用及场景案例等方面详细说明&#xff0c;帮助你高效创建和使用事务日志表。 一、日志…