Android 的 Handler
机制是跨线程通信 和异步消息处理 的核心框架,它构成了 Android 应用响应性和事件驱动模型的基础(如 UI 更新、后台任务协调)。其核心思想是 "消息队列 + 循环处理"。
核心组件及其关系
-
Handler
(处理器):- 角色: 消息的发送者 和处理者。
- 功能:
- 发送消息: 将
Message
或Runnable
对象放入与其关联的MessageQueue
中。方法包括sendMessage()
,post()
,sendMessageDelayed()
,postDelayed()
等。 - 处理消息: 实现
handleMessage(Message msg)
方法,定义当消息从队列中被取出时如何执行相应的操作。对于post(Runnable r)
,则是在关联线程中执行r.run()
。
- 发送消息: 将
-
Message
(消息):- 角色: 通信的载体。
- 内容: 包含需要传递的数据 (
what
,arg1
,arg2
,obj
,data
Bundle) 和目标Handler
(target
)。 - 优化: 通常通过
Message.obtain()
或Handler.obtainMessage()
从对象池中获取,避免频繁创建对象引起 GC。
-
MessageQueue
(消息队列):- 角色: 一个按执行时间排序 (主要是
when
字段)的优先级队列。 - 功能:
- 存储消息: 接收
Handler
发送来的Message
,并根据when
(绝对时间戳) 插入到队列的合适位置。 - 取出消息: 由关联的
Looper
循环调用next()
方法取出下一个待处理的消息。如果队列为空或下一个消息的执行时间未到,next()
会阻塞。
- 存储消息: 接收
- 关键特性:
单线程
操作(每个Looper
线程有且只有一个MessageQueue
)。
- 角色: 一个按执行时间排序 (主要是
-
Looper
(循环器):- 角色: 消息循环的引擎。负责驱动整个消息处理流程。
- 功能:
- 准备循环: 通过
Looper.prepare()
为当前线程创建一个Looper
(及其关联的MessageQueue
)。 - 启动循环: 调用
Looper.loop()
启动一个无限循环。在这个循环中:- 调用
MessageQueue.next()
获取下一条消息(可能阻塞)。 - 如果取到
null
(通常表示调用了quit
),退出循环。 - 否则,调用
msg.target.dispatchMessage(msg)
,将消息分发给发送它的Handler
处理。
- 调用
- 结束循环: 调用
Looper.quit()
(立即退出)或Looper.quitSafely()
(处理完已有消息后退出)。
- 准备循环: 通过
- 关键特性:
单线程
操作(每个Looper
线程有且只有一个Looper
)。- 主线程 (
ActivityThread
) 的Looper
在应用启动时由系统自动创建并启动 (Looper.prepareMainLooper()
,Looper.loop()
)。 - 其他线程需要手动调用
prepare()
和loop()
来创建和使用Looper
(如HandlerThread
)。
-
ThreadLocal
(线程局部存储):- 角色: 确保每个线程访问到它自己独有的
Looper
和MessageQueue
实例。 - 原理:
Looper
内部使用ThreadLocal
存储当前线程的Looper
对象。myLooper()
和getMainLooper()
都依赖于它。
- 角色: 确保每个线程访问到它自己独有的
工作流程详解
-
初始化 (通常在主线程或自定义线程):
-
主线程: 系统自动完成
Looper.prepareMainLooper()
和Looper.loop()
。 -
自定义线程:
javaclass WorkerThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); // 1. 为当前线程创建 Looper 和 MessageQueue mHandler = new Handler() { // 2. 创建 Handler,自动绑定到当前线程的 Looper @Override public void handleMessage(Message msg) { // 处理来自其他线程的消息 } }; Looper.loop(); // 3. 启动消息循环 (阻塞在此处) } }
-
-
发送消息 (例如从后台线程发往主线程更新 UI):
java// 假设在主线程初始化时保存了主线程 Handler: mainHandler new Thread(new Runnable() { @Override public void run() { // ... 后台工作 ... Message msg = mainHandler.obtainMessage(MSG_UPDATE_UI, data); mainHandler.sendMessage(msg); // 或 mainHandler.post(updateUiRunnable) // 消息被放入主线程的 MessageQueue } }).start();
-
消息循环处理 (
Looper.loop()
):-
在拥有
Looper
的线程中,loop()
方法无限循环:javapublic static void loop() { final Looper me = myLooper(); // 获取当前线程的 Looper final MessageQueue queue = me.mQueue; // 获取关联的 MessageQueue for (;;) { Message msg = queue.next(); // 1. 取消息 (可能阻塞) if (msg == null) { // 2. 收到退出信号 (null),退出循环 return; } try { msg.target.dispatchMessage(msg); // 3. 分发消息给 Handler 处理 } finally { msg.recycleUnchecked(); // 4. 回收消息到对象池 } } }
-
-
消息分发 (
Handler.dispatchMessage(Message msg)
):javapublic void dispatchMessage(Message msg) { if (msg.callback != null) { // 1. 优先处理 Message 自带的 Runnable (post(Runnable r)) handleCallback(msg); } else { if (mCallback != null) { // 2. 其次处理 Handler 构造时传入的 Callback if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); // 3. 最后调用 Handler 子类实现的 handleMessage() } }
底层原理与关键技术点
-
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()
会在超时或唤醒时返回。
- 问题: 当队列为空或下一个消息的执行时间 (
-
ThreadLocal
保证线程隔离:Looper
类内部有一个static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
。Looper.prepare()
调用sThreadLocal.set(new Looper(quitAllowed))
。Looper.myLooper()
调用sThreadLocal.get()
。- 这确保了每个线程调用
myLooper()
得到的都是自己线程创建的Looper
对象,从而每个线程都有自己独立的MessageQueue
和消息循环。
-
主线程的特殊性:
ActivityThread
的main()
方法是 Android 应用的入口点。- 在
main()
中,系统会调用Looper.prepareMainLooper()
(内部也是prepare(false)
) 创建主线程Looper
,并将其标记为"主 Looper"(set()
到sMainLooper
并标记mIsMain
)。 - 然后调用
Looper.loop()
启动主线程的消息循环。所有 UI 事件(触摸、绘制、生命周期回调)以及通过主线程Handler
发送的消息,都在这个循环中被处理。 这就是为什么在非 UI 线程直接更新 UI 会报错 - UI 操作必须在拥有 View 树的线程(通常是主线程)执行。
-
屏障消息 (
SyncBarrier
):- 目的: 允许异步消息 优先于同步消息执行。
- 实现:
- 调用
MessageQueue.postSyncBarrier()
会插入一个target
为null
的特殊Message
(屏障)。 - 在
next()
方法中,如果遇到屏障,它会跳过所有后续的同步消息 (msg.isAsynchronous() == false
) ,只查找异步消息 (msg.isAsynchronous() == true
) 或新的屏障。 - 当需要移除屏障时,调用
MessageQueue.removeSyncBarrier(token)
。
- 调用
- 应用场景: View 系统在请求布局 (
requestLayout()
) 和绘制 (draw()
) 时使用屏障,确保 UI 渲染相关的异步消息(如VSYNC
信号触发的绘制)能优先执行,避免被耗时的同步消息阻塞造成卡顿。
-
异步消息 (
Message.setAsynchronous(true)
):- 标记为异步的消息,在遇到同步屏障时可以被优先处理。
- 通常由系统内部使用(如
Choreographer
用于VSYNC
回调)。开发者也可以通过Handler
的特定构造函数 (Handler(looper, callback, async)
) 或Message.setAsynchronous(true)
发送异步消息。
-
对象池 (
Message
):- 为了避免频繁创建和销毁
Message
对象带来的 GC 开销,Message
内部维护了一个静态对象池 (链表结构)。 Message.obtain()
会从池中取出一个复用对象(如果可用),否则才新建。Message.recycle()
或recycleUnchecked()
(在Looper.loop()
的finally
块中调用) 会将处理完的消息放回池中。Handler
的obtainMessage()
系列方法内部也是调用Message.obtain()
。
- 为了避免频繁创建和销毁
-
HandlerThread
(便捷类):-
一个已经封装好了
Looper
的Thread
子类。使用它创建后台线程处理消息非常方便:javaHandlerThread handlerThread = new HandlerThread("MyHandlerThread"); handlerThread.start(); // 内部会调用 prepare() 和 loop() Handler handler = new Handler(handlerThread.getLooper()); handler.post(...); // 任务会在 handlerThread 这个后台线程执行
-
重要注意事项
-
内存泄漏:
- 典型场景: 在
Activity
中声明一个非静态内部类的Handler
。这个Handler
隐式持有外部Activity
的引用。 - 问题: 如果
Handler
的消息队列中还有未处理完的消息(尤其是延迟消息),这些消息持有Handler
引用 ->Handler
持有Activity
引用 -> 导致Activity
无法被 GC 回收,即使它已经被销毁。 - 解决方案:
- 使用
static
内部类 +WeakReference
持有Activity
。 - 在
Activity
的onDestroy()
中调用handler.removeCallbacksAndMessages(null)
清除队列中所有该Handler
的消息。
- 使用
- 典型场景: 在
-
ANR (Application Not Responding):
- 原因: 主线程的
Looper
在loop()
中处理消息 (dispatchMessage
或Runnable.run()
)。如果某个消息的处理耗时过长(超过 5 秒),会阻塞后续所有消息的处理,包括屏幕绘制和用户输入事件,系统就会弹出 ANR 对话框。 - 避免: 严禁在主线程执行耗时操作 (网络请求、大文件读写、复杂计算等)。耗时操作务必放在工作线程,完成后通过
Handler
通知主线程更新 UI。
- 原因: 主线程的
总结
Android 的 Handler
机制是一个基于生产者-消费者模型 构建的、围绕消息队列 和事件循环 的异步通信框架。Handler
负责发送消息,Message
是数据载体,MessageQueue
是存储和调度消息的优先级队列(底层依赖 epoll
实现高效阻塞/唤醒),Looper
是驱动消息循环的核心引擎(依赖 ThreadLocal
保证线程隔离)。主线程的消息循环由系统自动创建,是 UI 更新的生命线。理解其原理对于编写高效、响应流畅的 Android 应用至关重要,同时也要警惕内存泄漏和 ANR 问题。屏障消息和异步消息是系统优化 UI 性能的关键机制。HandlerThread
为后台线程使用 Handler
提供了便捷方式。