想象一下你开了一家小店(你的 Android App)。店里只有你一个人(主线程/UI 线程)负责所有事情:招呼客人(响应用户点击)、收银(处理业务逻辑)、打扫卫生(更新界面显示)。如果突然来了一大堆需要长时间处理的订单(比如下载图片、网络请求、复杂计算),你一个人埋头处理,店门口排长队等结账的客人(用户)就会觉得卡顿甚至以为店倒闭了(App 无响应 ANR)。
Handler 机制就是为了解决这个"一个人忙不过来还导致店面卡顿"的问题!它本质上是一个精巧的"任务中转站"和"消息快递员"系统。
一、为什么要用 Handler?
- 解决 UI 线程阻塞问题: Android 规定,只有主线程(UI 线程)才能直接更新界面 。如果主线程去做耗时操作(网络、文件读写、大计算),界面就会卡住不动,用户一点击,超过 5 秒没响应,系统就会弹出 ANR (Application Not Responding) 错误框,你的 App 就可能被强制关闭。Handler 允许你把耗时操作扔到后台线程 去做,做完后再把结果"通知"回主线程来更新 UI。
- 实现线程间通信: App 运行时会创建多个线程(主线程 + 后台工作线程)。不同线程之间不能直接访问对方的内存。Handler 提供了一种安全、有序、可靠的方式,让一个线程(如后台线程)可以发送"消息"或"任务"给另一个线程(如主线程)去处理。
- 实现延时任务: 你可以让一个任务在指定的时间之后才执行(比如 5 秒后隐藏一个提示条)。
- 实现周期性任务: 你可以让一个任务每隔一段时间就执行一次(比如每隔 1 秒更新一次计时器)。
二、Handler 机制的关键角色
理解 Handler 机制,首先要认识几个关键"角色",它们像一个小团队一样协同工作:
-
Message
(消息):-
是什么? 一个携带数据的"小包裹"或者"任务指令单"。你可以把它想象成一张快递单。
-
里面装什么?
what
: 一个整数标识符,用来区分不同类型的消息(比如:消息1 代表更新进度条,消息2 代表显示下载完成)。就像快递单上的"物品类型"。arg1
,arg2
: 两个整型参数,用于传递简单的数值信息(比如当前进度值 50)。obj
: 一个 Object 类型的参数,可以携带任意复杂的数据对象(比如下载好的图片 Bitmap 对象)。就像快递包裹里的实际物品。target
: (通常开发者不直接设置) 这个包裹最终要送给哪个Handler
处理。系统会自动设置。callback
: 一个Runnable
对象,也可以理解为一种特殊的"任务指令"(下篇源码会讲)。
-
怎么用? 通常用
Message.obtain()
或handler.obtainMessage()
来获取一个消息对象(复用池机制,高效),然后设置它的字段,最后通过Handler
发送出去。
-
-
Handler
(处理者/快递员):-
是什么? 消息的发送者 和最终处理者 。你可以把它想象成既是发件人又是收件点指定的快递员。
-
主要工作:
- 发送消息: 提供
sendMessage()
,sendMessageDelayed()
,post(Runnable)
,postDelayed(Runnable)
等方法,让你把Message
或Runnable
任务放入消息队列。你通常在创建 Handler 的线程或其他线程中调用这些发送方法。 - 处理消息: 需要你重写
handleMessage(Message msg)
方法。当 Looper 把属于这个 Handler 的消息从队列里取出来时,就会在这个 Handler 关联的线程 (通常是创建它的线程,比如主线程)里调用这个方法来处理消息(比如用msg.obj
里的 Bitmap 更新 ImageView)。
- 发送消息: 提供
-
关键点:
Handler
必须和一个特定的线程(通过Looper
)绑定。它决定了消息最终在哪个线程 执行handleMessage
。
-
-
MessageQueue
(消息队列):- 是什么? 一个按时间顺序排列 的、优先级队列(内部是单链表)。它就像一个无限长的传送带 或者快递站的分拣区。
- 干什么用? 存储所有通过
Handler
发送过来的Message
和Runnable
任务。队列中的消息按照**when
(执行时间戳)** 排序,时间小的(早执行的)排在前面。 - 谁管理? 由
Looper
管理。每个线程最多只有一个MessageQueue
。
-
Looper
(循环者/快递站调度员):-
是什么? 消息循环的核心驱动引擎。你可以把它想象成快递站里24小时不停歇工作的核心调度员。
-
主要工作:
- 准备循环: 线程需要调用
Looper.prepare()
来创建自己的Looper
和MessageQueue
。 - 启动循环: 调用
Looper.loop()
。这个方法内部是一个死循环for (;;) { ... }
。 - 不断检查: 在循环中,调度员(Looper)不停地检查传送带(MessageQueue):"有没有快递包裹(Message)到时间该送了?"。
- 取出分发: 如果有(且到时间了),就从队列头部取出一个包裹(Message),然后交给包裹上指定的快递员(
msg.target
,也就是发送它的 Handler)去派送(调用该 Handler 的dispatchMessage(msg)
方法,最终触发handleMessage(msg)
或Runnable.run()
)。 - 等待休息: 如果队列是空的,或者下一个包裹还没到派送时间,调度员(Looper)就让传送带暂时停下来(进入阻塞状态),节省 CPU 资源,直到有新的包裹到来或者有包裹到时间了才被唤醒。
- 准备循环: 线程需要调用
-
关键点:
- 主线程自带 Looper: Android 的主线程(UI 线程)在启动时已经自动创建并启动了
Looper
(ActivityThread.main()
方法里做了这事)。这就是为什么在主线程可以直接创建Handler
。 - 普通线程需手动创建: 如果你想在一个普通的后台线程使用 Handler 机制,必须 在该线程内先调用
Looper.prepare()
创建 Looper,再调用Looper.loop()
启动循环。线程结束后记得调用Looper.myLooper().quitSafely()
退出循环,否则线程会一直运行(内存泄漏!)。 - 一个线程一个 Looper: 每个线程最多只能有一个
Looper
(通过ThreadLocal
保证)。
- 主线程自带 Looper: Android 的主线程(UI 线程)在启动时已经自动创建并启动了
-
团队协作流程图:
scss
[后台线程] 或 [主线程本身]
|
| 1. 创建 Message / Runnable
| 2. 调用 Handler.sendMessage() / post()
v
[Handler] ----> 将 Message/Runnable 放入 ----> [MessageQueue] (属于某个线程)
^ |
| | 按时间排序
| v
| [Looper] (同一线程)
| |
| 4. Looper 取出消息,调用 Handler.dispatchMessage() | (在 Looper 所在线程执行!)
| |
| 5. Handler.handleMessage() 或 Runnable.run() 执行 | (在 Looper 所在线程执行!)
|_______________________________________|
三、Handler 如何使用?
场景 1:后台线程完成任务后更新 UI (最常用)
java
// 1. 在主线程创建 Handler (绑定到主线程的Looper)
private Handler mHandler = new Handler(Looper.getMainLooper()) { // 显式指定主线程Looper,更清晰
@Override
public void handleMessage(Message msg) {
// 这个代码在主线程执行!可以安全更新UI
switch (msg.what) {
case MSG_DOWNLOAD_COMPLETE:
Bitmap bitmap = (Bitmap) msg.obj;
mImageView.setImageBitmap(bitmap); // 更新ImageView
break;
case MSG_UPDATE_PROGRESS:
int progress = msg.arg1;
mProgressBar.setProgress(progress); // 更新进度条
break;
}
}
};
private static final int MSG_DOWNLOAD_COMPLETE = 1;
private static final int MSG_UPDATE_PROGRESS = 2;
// 在某个后台线程中执行耗时操作 (比如点击按钮触发)
private void startDownloadInBackground() {
new Thread(new Runnable() {
@Override
public void run() {
// 模拟下载过程
for (int i = 0; i <= 100; i += 10) {
// ... 耗时下载操作 ...
// 2. 在后台线程发送进度更新消息给主线程Handler
Message progressMsg = mHandler.obtainMessage(MSG_UPDATE_PROGRESS, i, 0); // arg1 = i
mHandler.sendMessage(progressMsg);
// 模拟下载耗时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 3. 下载完成,发送结果消息给主线程Handler
Bitmap resultBitmap = ...; // 假设下载好的图片
Message completeMsg = mHandler.obtainMessage(MSG_DOWNLOAD_COMPLETE, resultBitmap);
mHandler.sendMessage(completeMsg);
}
}).start();
}
解释:
-
在主线程创建
Handler
(mHandler
),并重写handleMessage
。这个 Handler 自动绑定到主线程的 Looper。handleMessage
里的代码一定在主线程执行,可以安全操作 UI。 -
启动一个新线程 (
startDownloadInBackground
里的new Thread
) 执行耗时的下载任务。 -
在后台线程中:
- 使用
mHandler.obtainMessage(...)
获取一个Message
对象(复用池,高效)。 - 设置消息标识
what
(MSG_UPDATE_PROGRESS
或MSG_DOWNLOAD_COMPLETE
)。 - 通过
arg1
携带进度值,或通过obj
携带下载好的 Bitmap。 - 调用
mHandler.sendMessage(msg)
将消息发送出去。这个消息会被放入主线程 的MessageQueue
。
- 使用
-
主线程的
Looper
一直在循环检查自己的MessageQueue
。 -
当它发现有新消息(进度更新或下载完成),且消息到时间了(这里是立即执行),就从队列中取出该消息。
-
取出消息后,
Looper
调用消息target
字段指向的Handler
(即mHandler
) 的dispatchMessage
方法。 -
dispatchMessage
内部会根据情况调用handleMessage(msg)
(我们重写的方法) 或Runnable.run()
。 -
handleMessage
在主线程执行,根据msg.what
判断消息类型,取出数据 (msg.arg1
,msg.obj
),安全地更新ProgressBar
或ImageView
。
场景 2:在主线程执行延时任务
typescript
// 在主线程创建 Handler (可省略 Looper.getMainLooper() 参数,默认即主线程)
private Handler mHandler = new Handler();
// 5秒后执行一个任务(比如隐藏提示信息)
private void scheduleHideMessage() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
// 这个代码在主线程执行
mTextView.setVisibility(View.GONE); // 5秒后隐藏TextView
}
}, 5000); // 延迟 5000 毫秒 (5秒)
}
// 取消延时任务 (在需要时调用,如界面销毁时)
private void cancelScheduledTask() {
mHandler.removeCallbacksAndMessages(null); // 移除该Handler所有任务
// 或 mHandler.removeCallbacks(specificRunnable); // 移除特定Runnable
}
解释:
- 创建 Handler (默认绑定主线程 Looper)。
- 调用
postDelayed(Runnable r, long delayMillis)
将一个Runnable
任务发送到主线程的MessageQueue
中,并指定它在当前时间+ 5000ms
后执行。 - 主线程
Looper
在循环中发现这个任务的时间到了,就取出它并执行其run()
方法(在主线程)。 removeCallbacks...
方法用于在任务执行前取消它,避免无效操作或内存泄漏(尤其是在 Activity/Fragment 销毁时)。
场景 3:在自定义后台线程使用 Handler/Looper
typescript
public class WorkerThread extends Thread {
private Looper mLooper; // 保存线程的Looper引用
private Handler mWorkerHandler; // 线程自己的Handler
@Override
public void run() {
// 1. 准备Looper (创建该线程的Looper和MessageQueue)
Looper.prepare();
// 2. 创建Handler, 它会自动绑定到当前线程(new时默认使用当前线程的Looper)
mWorkerHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 这个代码在 WorkerThread 线程执行!
// 处理发送到这个Handler的消息 (后台任务)
Log.d("WorkerThread", "Handling message: " + msg.what);
// ... 执行具体的后台任务 ...
}
};
// 3. 获取当前线程的Looper并保存 (可选,用于外部获取/退出)
mLooper = Looper.myLooper();
// 4. 启动消息循环 (开始检查MessageQueue)
Looper.loop(); // 这个调用会阻塞,直到quit
// loop() 方法退出后,线程会结束
Log.d("WorkerThread", "Worker thread exiting.");
}
// 提供给外部发送消息到该工作线程的方法
public void sendTaskToWorker(int taskId) {
if (mWorkerHandler != null) {
mWorkerHandler.sendEmptyMessage(taskId);
}
}
// 安全退出工作线程
public void quit() {
if (mLooper != null) {
mLooper.quitSafely(); // 安全退出Looper, 会处理完队列中已有的消息
// mLooper.quit(); // 立即退出,不处理未到时的消息 (可能不安全)
}
}
}
// 使用示例 (在主线程或其他线程)
WorkerThread worker = new WorkerThread();
worker.start(); // 启动工作线程 (内部会创建Looper和Handler)
// ... 稍后 ...
worker.sendTaskToWorker(100); // 发送任务ID=100给工作线程处理
// 当不再需要工作线程时 (如Activity销毁)
worker.quit();
解释:
-
创建自定义线程
WorkerThread
。 -
在
run()
方法中:- 调用
Looper.prepare()
:为该线程创建唯一的Looper
和MessageQueue
。 - 创建
Handler
(mWorkerHandler
):此时创建会自动绑定到当前线程 (即WorkerThread
)的Looper
。重写handleMessage
来处理发送到这个 Handler 的消息,这些消息的处理代码将在WorkerThread
中执行。 - 调用
Looper.myLooper()
保存 Looper 引用(用于后续退出)。 - 调用
Looper.loop()
:启动消息循环。这个方法是一个阻塞调用,线程会停在这里,不断从MessageQueue
取消息处理,直到调用looper.quit()
。
- 调用
-
提供
sendTaskToWorker
方法,让外部线程(如主线程)可以通过mWorkerHandler
向这个工作线程发送消息。 -
提供
quit()
方法,在适当时候(如线程不再需要时)调用mLooper.quitSafely()
来退出Looper
循环。quitSafely()
会处理完队列中所有已到时的消息,然后让loop()
方法返回,线程自然结束。非常重要,避免线程泄漏!
四、使用 Handler 的重要注意事项
-
内存泄漏 (Memory Leak): 这是 Handler 最常见的坑!
-
问题: 非静态内部类(包括匿名内部类)的
Handler
会隐式持有其外部类(通常是Activity
或Fragment
)的引用。如果Handler
的消息队列中还有未处理完的延时消息,而外部类(如 Activity)需要被销毁了,由于Handler
持有 Activity 引用 →Message
持有Handler
引用 →MessageQueue
持有Message
引用 →Looper
(通常是主线程的) 持有MessageQueue
引用。只要主线程还活着,Looper 就活着,这条引用链就让垃圾回收器 (GC) 无法回收 Activity,导致内存泄漏。 -
解决:
- 使用静态内部类 + WeakReference: 将
Handler
声明为static
的,这样它就不会持有外部类实例的引用了。在Handler
内部通过WeakReference
来弱引用Activity/Fragment
。在handleMessage
中,先检查弱引用是否还能获取到 Activity(get()
不为 null),如果为 null 说明 Activity 已被销毁,则不再处理消息。 - 在外部类销毁时移除消息: 在
Activity
的onDestroy()
方法中调用handler.removeCallbacksAndMessages(null)
,移除该 Handler 关联的所有未处理消息,断开 Message 对 Handler 的引用链。
- 使用静态内部类 + WeakReference: 将
-
-
主线程 Looper 永不退出: 主线程的 Looper 在 App 整个生命周期内都不会退出 (
loop()
不会返回)。不要尝试在主线程调用Looper.myLooper().quit()
,这会导致崩溃! -
后台线程 Looper 必须手动退出: 对于自己创建的、使用了
Looper.loop()
的后台线程,必须 在不再需要时调用looper.quitSafely()
来退出循环,否则线程会一直运行(阻塞在loop()
),导致线程泄漏和资源浪费。 -
避免在后台线程创建绑定主线程 Looper 的 Handler: 虽然技术上可以(如
new Handler(Looper.getMainLooper())
在任何线程创建),但要注意发送的消息最终在主线程处理,里面的操作不能耗时,否则又会导致主线程卡顿。发送消息本身是线程安全的。 -
post(Runnable)
vssendMessage(Message)
:post(Runnable r)
本质上也是发送了一个特殊的Message
(其callback
字段设置为r
)。- 当
Handler
处理消息时,如果Message
的callback
不为 null(即post
发送的),会优先执行Runnable.run()
。 - 如果
callback
为 null(即sendMessage
发送的),才会调用handleMessage(Message msg)
。 - 使用
post
对于简单的任务代码更简洁,不需要定义what
常量。复杂的、需要携带多个数据的任务用sendMessage
更合适。
五、总结
-
Handler 机制的核心目标: 安全地在不同线程间传递消息和执行任务 ,特别是解决后台线程执行耗时任务,主线程安全更新 UI 的问题。
-
四大核心组件:
Message
: 携带数据和指令的"包裹"。Handler
: 消息的发送者 和处理者 。绑定到特定线程的 Looper,决定了消息在哪个线程处理。MessageQueue
: 按时间排序存储消息的"传送带"。Looper
: 消息循环的"引擎"。不断检查队列,取出消息分发给对应的 Handler 处理。主线程自带 Looper,普通线程需手动创建和启动 (prepare()
+loop()
),并在结束时退出 (quitSafely()
)。
-
基本使用模式:
- (在目标线程) 创建
Handler
,重写handleMessage
处理逻辑(或使用post(Runnable)
)。 - (在发送线程) 创建
Message
或Runnable
,通过Handler
的sendMessageXXX()
或postXXX()
方法发送。 - 目标线程的
Looper
会取出消息,在目标线程 调用Handler
的处理逻辑。
- (在目标线程) 创建
-
关键注意事项: 谨防内存泄漏 (静态 Handler + WeakReference + 及时移除消息)和后台线程 Looper 泄漏 (及时
quitSafely
)。