Handler基本概念
一、Handler 是什么
1.1 定义
Handler 是 Android 消息机制中的核心类,用于在不同线程之间传递和处理消息。它通过结合 Message、MessageQueue 和 Looper,提供了一套完整的异步消息处理框架。
核心作用:
- 线程间通信:将任务从工作线程切换到主线程执行
- 延迟执行:支持延迟消息和定时任务
- 消息调度:管理消息队列,按时间顺序处理消息
为什么需要 Handler:
- Android UI 线程不安全:只能在主线程更新 UI
- 耗时操作必须在子线程:避免 ANR(Application Not Responding)
- 线程切换需求:子线程完成任务后需要通知主线程更新 UI
1.2 在 Android 架构中的定位
Handler 位于 Android Framework 层,是连接应用层和底层消息机制的桥梁。
应用层 Activity/Fragment
↓ 使用
Handler (Framework 层)
↓ 依赖
MessageQueue + Looper (Framework 层)
↓ 底层实现
Native MessageQueue + epoll (Native 层)
典型应用场景:
- 子线程更新 UI:网络请求完成后更新界面
- 定时任务:倒计时、轮询刷新
- 线程协作:多线程任务协调
- 系统服务:ActivityManagerService、WindowManagerService 内部大量使用
二、Handler 四大核心类
Handler 消息机制由四个核心类协作完成,理解它们的关系是掌握 Handler 的关键。
2.1 四大核心类关系图
┌─────────────────────────────────────────────────┐
│ Thread │
│ ┌──────────────────────────────────────────┐ │
│ │ Looper │ │
│ │ - sThreadLocal: ThreadLocal<Looper> │ │
│ │ - mQueue: MessageQueue │ │
│ │ - loop(): 循环取消息 │ │
│ └──────────────────────────────────────────┘ │
│ ↓ 包含 │
│ ┌──────────────────────────────────────────┐ │
│ │ MessageQueue │ │
│ │ - mMessages: Message (链表头) │ │
│ │ - next(): 取出下一条消息 │ │
│ │ - enqueueMessage(): 插入消息 │ │
│ └──────────────────────────────────────────┘ │
│ ↑ 入队 ↓ 出队 │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ Handler │ │ Message │ │
│ │ - mLooper│ │ - target:Handler │ │
│ │ - mQueue │ │ - when: long │ │
│ │ - send() │ │ - next: Message │ │
│ │ - post() │ │ │ │
│ └──────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────┘
2.2 Handler - 消息发送与处理者
源码路径: frameworks/base/core/java/android/os/Handler.java
核心职责:
- 发送消息到 MessageQueue
- 处理 Looper 分发的消息
关键字段:
// Handler.java (Android 12)
public class Handler {
final Looper mLooper; // 关联的 Looper
final MessageQueue mQueue; // 关联的消息队列
final Callback mCallback; // 消息处理回调
final boolean mAsynchronous; // 是否是异步 Handler
public Handler(@Nullable Callback callback, boolean async) {
mLooper = Looper.myLooper(); // 获取当前线程的 Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; // 获取 Looper 的消息队列
mCallback = callback;
mAsynchronous = async;
}
}
关键方法:
| 方法 | 作用 | 使用场景 |
|---|---|---|
sendMessage(Message) | 发送消息 | 需要传递数据 |
sendEmptyMessage(int) | 发送空消息 | 只需要消息标识 |
post(Runnable) | 发送 Runnable | 简单的任务执行 |
sendMessageDelayed() | 延迟发送消息 | 定时任务 |
handleMessage(Message) | 处理消息 | 子类重写处理逻辑 |
removeMessages(int) | 移除消息 | 取消待执行的任务 |
2.3 Message - 消息载体
源码路径: frameworks/base/core/java/android/os/Message.java
核心职责: 封装需要传递的数据和目标 Handler。
关键字段:
// Message.java (Android 12)
public final class Message implements Parcelable {
public int what; // 消息标识
public int arg1; // 整型参数1
public int arg2; // 整型参数2
public Object obj; // 任意对象
public long when; // 消息执行时间
public Bundle data; // 复杂数据
/*package*/ Handler target; // 目标 Handler
/*package*/ Runnable callback;// Runnable 回调
/*package*/ Message next; // 链表指针
private static Message sPool; // 消息池头节点
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50; // 最大池容量
}
消息池机制:
Message 采用享元模式实现对象池复用,避免频繁创建销毁对象。内部维护一个最大容量为 50 的单链表结构对象池。
核心特性:
- obtain():从对象池获取可复用的 Message,池为空则创建新对象
- recycleUnchecked():清空字段数据并放回对象池
- 最佳实践:使用
Message.obtain()而非new Message()
详细源码实现:详见
../04-Message对象池/Message对象池原理.md
2.4 MessageQueue - 消息队列
源码路径: frameworks/base/core/java/android/os/MessageQueue.java
核心职责: 维护消息链表,按时间顺序管理消息。
关键字段:
// MessageQueue.java (Android 12)
public final class MessageQueue {
private long mPtr; // Native 层 NativeMessageQueue 指针
Message mMessages; // 消息链表头节点
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<>();
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;
private boolean mBlocked;
private int mNextBarrierToken;
}
核心数据结构:
MessageQueue 本质是一个按时间排序的单链表:
mMessages → [when=100] → [when=200] → [when=500] → null
(最早执行) (最晚执行)
关键方法:
- enqueueMessage(Message, long):将消息按时间戳插入链表
- next():取出下一条可执行消息,无消息时阻塞
- postSyncBarrier():设置同步屏障,优先处理异步消息
重要特性:
- 优先队列:按
when时间戳排序,最早执行的消息在链表头部 - 阻塞唤醒:底层使用 epoll 机制,无消息时阻塞,有消息时唤醒
- 同步屏障:支持同步屏障机制优先处理异步消息
消息入队详细流程:详见
../03-MessageQueue队列管理/01-消息入队enqueueMessage.md消息出队详细流程:详见../03-MessageQueue队列管理/02-消息出队next.mdepoll 阻塞唤醒机制:详见../03-MessageQueue队列管理/04-epoll机制.md
2.5 Looper - 消息循环器
源码路径: frameworks/base/core/java/android/os/Looper.java
核心职责: 在当前线程开启消息循环,不断从 MessageQueue 取消息分发给 Handler。
关键字段:
// Looper.java (Android 12)
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // 主线程 Looper
final MessageQueue mQueue;
final Thread mThread;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
}
核心方法:
// Looper.java (Android 12)
// 1. 准备 Looper
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
// 2. 开启消息循环
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // 阻塞式取消息
if (msg == null) return; // 退出循环
msg.target.dispatchMessage(msg); // 分发给 Handler
msg.recycleUnchecked(); // 回收消息
}
}
// 3. 获取当前线程 Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
重要特性:
- 线程本地存储:每个线程最多一个 Looper
- 无限循环:
for(;;)持续运行,不会退出(除非调用quit()) - 主线程 Looper:由
ActivityThread.main()自动创建
2.6 四大核心类协作流程
┌─────────────┐
│ 1. Handler │ sendMessage(msg)
│ 创建消息 │────────┐
└─────────────┘ │
↓
┌─────────────────────────────────┐
│ 2. MessageQueue │
│ 按时间顺序插入链表 │
│ [msg1] → [msg2] → [msg3] │
└─────────────────────────────────┘
│
↓
┌─────────────────────────────────┐
│ 3. Looper.loop() │
│ for(;;) { │
│ msg = queue.next(); │
│ msg.target.dispatchMessage │
│ } │
└─────────────────────────────────┘
│
↓
┌─────────────────────────────────┐
│ 4. Handler │
│ handleMessage(msg) │
│ 处理消息 │
└─────────────────────────────────┘
三、Handler 基本工作机制
3.1 完整工作流程概述
Handler 消息机制的核心是生产者-消费者模型:
- 准备阶段:线程调用
Looper.prepare()创建 Looper 和 MessageQueue - 发送阶段:Handler 将 Message 插入 MessageQueue
- 循环阶段:
Looper.loop()不断从 MessageQueue 取消息 - 处理阶段:Looper 将消息分发给对应的 Handler 处理
时序图:
Thread A (子线程) MessageQueue Thread B (主线程 + Looper)
│ │ │
│ handler.sendMessage() │ │
│─────────────────────────→│ │
│ │ enqueueMessage() │
│ │ (插入链表) │
│ │ │
│ │←───────────────────────│
│ │ queue.next() │
│ │ │
│ │───────────────────────→│
│ │ 返回 Message │
│ │ │
│ │ │
│ │ │ handler.dispatchMessage()
│ │ │ handleMessage()
│ │ │ (处理消息)
3.2 线程切换原理
核心问题:为什么 Handler 能实现线程切换?
// 示例代码
new Thread(() -> {
// 子线程
handler.sendMessage(msg); // ← 在子线程发送
}).start();
// Handler 在主线程创建
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// ← 在主线程执行
textView.setText("更新 UI");
}
};
原理解析:
// Handler.java (Android 12)
public Handler(@NonNull Looper looper) {
mLooper = looper; // ← 关键:绑定主线程 Looper
mQueue = looper.mQueue; // ← 使用主线程的 MessageQueue
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue; // ← 插入主线程的队列
return enqueueMessage(queue, msg, uptimeMillis);
}
关键点:
- Handler 创建时绑定了主线程的 Looper
- 无论在哪个线程调用
sendMessage(),消息都会插入主线程的 MessageQueue - 主线程的 Looper 循环取出消息后,在主线程调用
handleMessage()
3.3 消息处理的三级分发
Handler 处理消息时有三级优先级:
// Handler.java (Android 12)
public void dispatchMessage(@NonNull Message msg) {
// 优先级1:Message 自带的 callback (Runnable)
if (msg.callback != null) {
handleCallback(msg);
} else {
// 优先级2:Handler 的 mCallback
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return; // 返回 true 则拦截
}
}
// 优先级3:Handler 子类重写的 handleMessage()
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
分发优先级:
| 优先级 | 来源 | 触发条件 | 典型使用 |
|---|---|---|---|
| 1 | msg.callback | handler.post(Runnable) | 简单任务 |
| 2 | handler.mCallback | 创建 Handler 时传入 | 拦截处理 |
| 3 | handleMessage() | 子类重写 | 常规使用 |
示例:
// 优先级1:post 方式
handler.post(() -> {
// msg.callback.run()
textView.setText("优先级1");
});
// 优先级2:Callback 拦截
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
// 返回 true 拦截,不再调用优先级3
return true;
}
});
// 优先级3:子类重写
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 常规处理
}
};
四、源码关键实现细节
4.1 ThreadLocal 实现 Looper 线程隔离
问题:如何保证每个线程只有一个 Looper?
// Looper.java (Android 12)
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(true));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
ThreadLocal 原理:
// ThreadLocal.java (Android 12)
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 获取线程的 Map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
return (T)e.value;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 每个 Thread 对象都有独立的 Map
}
核心机制:
- 每个 Thread 内部有一个
ThreadLocalMap - ThreadLocal 作为 key,Looper 作为 value
- 不同线程的 Map 互不影响,实现隔离
4.2 MessageQueue 的阻塞与唤醒
问题:队列为空时如何避免空转浪费 CPU?
核心机制:
MessageQueue 底层使用 Linux epoll 机制实现高效的阻塞与唤醒:
// MessageQueue.java - next() 方法核心流程
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis); // 阻塞等待
synchronized (this) {
Message msg = mMessages;
if (msg != null && now >= msg.when) {
return msg; // 返回可执行消息
} else if (msg != null) {
nextPollTimeoutMillis = msg.when - now; // 计算等待时间
} else {
nextPollTimeoutMillis = -1; // 无消息,无限等待
}
}
}
阻塞时机:
- 队列为空:
nextPollTimeoutMillis = -1,无限阻塞 - 有延迟消息:阻塞到消息执行时间
- 有即时消息:立即返回
唤醒条件:
- 新消息插入队列头部
- 消息队列从空变为非空
epoll 详细实现:详见
../03-MessageQueue队列管理/04-epoll机制.md消息出队完整流程:详见../03-MessageQueue队列管理/02-消息出队next.md
4.3 主线程 Looper 的创建时机
问题:主线程的 Looper 何时创建?
// ActivityThread.java (Android 12)
public static void main(String[] args) {
// ...
Looper.prepareMainLooper(); // ← 创建主线程 Looper
// ...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// ...
Looper.loop(); // ← 开启主线程消息循环
throw new RuntimeException("Main thread loop unexpectedly exited");
}
主线程 Looper 特点:
// Looper.java (Android 12)
public static void prepareMainLooper() {
prepare(false); // quitAllowed = false,主线程 Looper 不允许退出
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
关键点:
- 应用启动时,
ActivityThread.main()自动创建主线程 Looper - 主线程 Looper 不允许退出(
quitAllowed = false) - 可通过
Looper.getMainLooper()在任意线程获取主线程 Looper
五、实战应用场景
5.1 子线程更新 UI
public class NetworkActivity extends AppCompatActivity {
private TextView tvResult;
private Handler mainHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network);
tvResult = findViewById(R.id.tv_result);
// 创建主线程 Handler
mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
String result = (String) msg.obj;
tvResult.setText(result); // 在主线程更新 UI
}
}
};
loadData();
}
private void loadData() {
new Thread(() -> {
// 模拟网络请求
try {
Thread.sleep(2000);
String data = "服务器返回数据";
// 发送消息到主线程
Message msg = Message.obtain();
msg.what = 1;
msg.obj = data;
mainHandler.sendMessage(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
5.2 延迟任务与定时轮询
public class CountdownActivity extends AppCompatActivity {
private TextView tvCountdown;
private Handler handler;
private int countdown = 60;
private Runnable countdownTask = new Runnable() {
@Override
public void run() {
if (countdown > 0) {
tvCountdown.setText(countdown + "秒");
countdown--;
handler.postDelayed(this, 1000); // 延迟1秒再次执行
} else {
tvCountdown.setText("倒计时结束");
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_countdown);
tvCountdown = findViewById(R.id.tv_countdown);
handler = new Handler(Looper.getMainLooper());
handler.post(countdownTask); // 开始倒计时
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacks(countdownTask); // 移除任务,避免内存泄漏
}
}
5.3 HandlerThread 的使用
HandlerThread 是 Android 提供的封装了 Looper 的 Thread 类。
public class HandlerThreadExample {
private HandlerThread handlerThread;
private Handler workHandler;
public void start() {
// 创建 HandlerThread
handlerThread = new HandlerThread("WorkThread");
handlerThread.start();
// 获取 HandlerThread 的 Looper
workHandler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
// 在子线程执行耗时任务
processData(msg.obj);
}
};
}
public void doWork(Object data) {
Message msg = Message.obtain();
msg.obj = data;
workHandler.sendMessage(msg);
}
private void processData(Object data) {
// 耗时操作,在子线程执行
try {
Thread.sleep(1000);
Log.d("HandlerThread", "处理数据: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void stop() {
handlerThread.quitSafely(); // 安全退出
}
}
HandlerThread 源码:
// HandlerThread.java (Android 12)
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare(); // 创建 Looper
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll(); // 通知 getLooper() 方法
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop(); // 开启循环
mTid = -1;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait(); // 等待 run() 方法创建 Looper
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
}
六、面试题精选
6.1 基础题(P5 级别)
问题1:Handler、Looper、MessageQueue 的关系是什么?
标准答案(30秒): Handler 消息机制由三个核心类组成:Handler 负责发送和处理消息,MessageQueue 负责存储消息队列,Looper 负责循环取消息。它们的关系是:一个线程只有一个 Looper,一个 Looper 包含一个 MessageQueue,可以有多个 Handler 共享同一个 Looper 的 MessageQueue。发送消息时 Handler 将消息插入 MessageQueue,Looper 循环从 MessageQueue 取消息后分发给对应的 Handler 处理。
追问1:一个线程可以有几个 Looper?几个 Handler?
答案: 一个线程只能有一个 Looper,但可以有多个 Handler。
原因:
- Looper 通过 ThreadLocal 存储,
Looper.prepare()时会检查当前线程是否已存在 Looper,如果存在则抛出异常。 - Handler 创建时需要指定 Looper,多个 Handler 可以绑定同一个 Looper,共享同一个 MessageQueue。
追问2:为什么主线程不需要调用 Looper.prepare()?
答案:
主线程的 Looper 在应用启动时已经自动创建。应用启动时会执行 ActivityThread.main() 方法,其中调用了 Looper.prepareMainLooper() 创建主线程 Looper,并调用 Looper.loop() 开启消息循环。所以开发者在主线程使用 Handler 时不需要手动调用 Looper.prepare()。
问题2:Handler 如何实现线程切换?
标准答案(30秒): Handler 实现线程切换的关键在于 Handler 创建时绑定的 Looper。Handler 在哪个线程创建就会绑定那个线程的 Looper,后续无论在哪个线程调用 sendMessage,消息都会被插入到创建时绑定的 Looper 的 MessageQueue 中,最终由 Looper 所在的线程取出并执行 handleMessage。例如主线程创建 Handler 后,子线程调用 sendMessage,消息会插入主线程 MessageQueue,由主线程的 Looper 取出后在主线程执行,从而实现线程切换。
追问1:Handler 在子线程创建时需要注意什么?
答案:
子线程默认没有 Looper,需要手动调用 Looper.prepare() 创建 Looper,然后创建 Handler,最后调用 Looper.loop() 开启消息循环。使用完毕后还需要调用 Looper.quit() 或 Looper.quitSafely() 退出循环,释放资源。
示例:
new Thread(() -> {
Looper.prepare(); // 1. 创建 Looper
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
}
};
Looper.loop(); // 2. 开启循环
}).start();
追问2:如何在子线程向主线程发送消息?
答案: 有两种方式:
- 使用主线程 Looper 创建 Handler:
new Handler(Looper.getMainLooper()) - 在主线程创建 Handler 并传递给子线程使用
// 方式1:使用主线程 Looper
Handler mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 在主线程执行
}
};
new Thread(() -> {
mainHandler.sendMessage(msg); // 子线程发送
}).start();
问题3:Message 的 obtain 方法有什么作用?
标准答案(30秒): Message.obtain() 用于从消息池中获取可复用的 Message 对象,避免频繁创建销毁对象。Message 内部维护了一个最大容量为 50 的对象池,使用单链表结构存储。当调用 obtain 时会从池中取出一个 Message 复用,如果池为空则创建新对象。消息处理完毕后会自动调用 recycleUnchecked 方法清空数据并放回池中。使用 obtain 而非 new Message 能减少内存分配,提升性能。
追问1:Message 对象池的最大容量是多少?为什么要限制容量?
答案: 最大容量是 50 个。限制容量的原因:
- 避免内存占用过大:Message 对象可能携带大量数据(Bundle、Object 等),无限制缓存会占用过多内存
- 平衡性能与内存:50 个对象足够应对大部分高频消息场景
- 防止内存泄漏:超过容量的对象会被 GC 回收,避免长期持有
追问2:Message 回收时为什么要清空所有字段?
答案: 防止内存泄漏。Message 可能持有 Activity、View 等对象的引用,如果不清空直接放回对象池,即使外部不再使用该 Message,这些对象也无法被 GC 回收,导致内存泄漏。清空字段(what、obj、data、callback、target 等)可以断开引用链,使对象能够正常回收。
6.2 进阶题(P6+ 级别)
问题4:Handler 导致的内存泄漏原因及解决方案?
标准答案(30秒): Handler 内存泄漏的根本原因是消息队列持有 Message,Message 持有 Handler,非静态内部类 Handler 持有外部类 Activity 引用。当 Activity 销毁时,如果消息队列中还有待处理的消息,整个引用链无法释放导致泄漏。解决方案:一是使用静态内部类加弱引用,二是在 Activity 销毁时移除所有消息。推荐使用静态内部类方式,既能避免泄漏又能保证消息正常处理。
详细分析:
泄漏链路:
MessageQueue → Message → Handler → Activity
↑ ↓
└──────────── 无法释放 ─────────────┘
错误示例:
public class LeakActivity extends AppCompatActivity {
// 非静态内部类持有 Activity 引用
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 发送延迟消息
handler.sendEmptyMessageDelayed(1, 60000); // 60秒后执行
}
// Activity 销毁时,消息队列中的消息仍持有 Handler 引用
// Handler 持有 Activity 引用,导致 Activity 无法回收
}
解决方案1:静态内部类 + 弱引用
public class SafeActivity extends AppCompatActivity {
private TextView textView;
private SafeHandler handler;
private static class SafeHandler extends Handler {
private final WeakReference<SafeActivity> weakReference;
public SafeHandler(SafeActivity activity) {
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
SafeActivity activity = weakReference.get();
if (activity != null) {
activity.textView.setText("更新 UI");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textView = findViewById(R.id.tv_text);
handler = new SafeHandler(this);
handler.sendEmptyMessageDelayed(1, 60000);
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null); // 移除所有消息
}
}
解决方案2:Activity 销毁时移除消息
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null); // 参数为 null 表示移除所有消息
}
追问1:为什么使用静态内部类就不会持有外部类引用?
答案:
Java 中非静态内部类会隐式持有外部类的引用(通过 OuterClass.this),而静态内部类不会。静态内部类在编译后是一个独立的类,不依赖于外部类实例,因此不会持有外部类引用。如果需要访问外部类,必须显式传递引用,使用弱引用可以避免强引用导致的泄漏。
追问2:removeCallbacksAndMessages(null) 为什么能移除所有消息?
答案:
// Handler.java (Android 12)
public final void removeCallbacksAndMessages(@Nullable Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
// MessageQueue.java
void removeCallbacksAndMessages(Handler h, Object object) {
synchronized (this) {
Message p = mMessages;
// 移除链表头部匹配的消息
while (p != null && p.target == h && (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// 移除链表中间匹配的消息
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
当 object == null 时,条件 object == null || p.obj == object 始终为 true,会移除所有 target == h 的消息。
问题5:Looper.loop() 是死循环,为什么不会卡死主线程?
标准答案(30秒): Looper.loop() 虽然是死循环,但不会卡死主线程,原因有三点:第一,loop 内部调用 MessageQueue.next() 时,如果没有消息会通过 epoll 机制进入阻塞状态释放 CPU,不会空转;第二,Android 应用的事件驱动模型决定了主线程必须持续运行,所有 UI 事件、生命周期回调都通过消息机制执行;第三,真正耗时的操作应该在子线程执行,主线程只负责快速处理 UI 相关任务,单个消息执行时间短不会阻塞循环。
详细分析:
关键机制1:epoll 阻塞
// MessageQueue.java (Android 12)
Message next() {
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis); // 阻塞等待
synchronized (this) {
Message msg = mMessages;
if (msg != null) {
if (now < msg.when) {
// 计算阻塞时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
return msg; // 返回消息
}
} else {
nextPollTimeoutMillis = -1; // 无消息,无限等待
}
}
}
}
阻塞时机:
- 队列为空:
nextPollTimeoutMillis = -1,无限阻塞 - 有延迟消息:
nextPollTimeoutMillis = msg.when - now,阻塞到消息执行时间 - 有即时消息:立即返回,不阻塞
关键机制2:事件驱动模型
Android 主线程的所有任务都是通过消息触发:
| 事件类型 | 触发方式 | 消息来源 |
|---|---|---|
| 点击事件 | 用户触摸屏幕 | InputEventReceiver |
| 生命周期 | AMS 调用 | ActivityThread.H |
| View 绘制 | 请求刷新 | Choreographer |
| 广播接收 | 系统发送 | ActivityThread.H |
主线程消息处理示例:
// ActivityThread.java (Android 12)
class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int RESUME_ACTIVITY = 107;
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY:
handleLaunchActivity((ActivityClientRecord) msg.obj);
break;
case PAUSE_ACTIVITY:
handlePauseActivity((IBinder) msg.obj, false, ...);
break;
// ...
}
}
}
追问1:如果 Looper.loop() 退出会发生什么?
答案:
主线程的 Looper 退出会导致应用崩溃。ActivityThread.main() 方法在 Looper.loop() 后面有一行代码:
public static void main(String[] args) {
// ...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
如果主线程 Looper 退出循环,会抛出 RuntimeException 导致应用崩溃。实际上主线程 Looper 创建时设置了 quitAllowed = false,不允许退出。
追问2:epoll 机制和 Object.wait() 有什么区别?
答案:
| 对比项 | epoll | Object.wait() |
|---|---|---|
| 层级 | Linux 系统调用 | Java 对象方法 |
| 唤醒方式 | I/O 事件(写管道) | notify/notifyAll |
| 性能 | 更高效,内核态阻塞 | 用户态阻塞 |
| 应用场景 | I/O 多路复用 | 线程同步 |
MessageQueue 使用 epoll 而非 wait 的原因:
- 跨进程唤醒:系统服务可以通过写管道唤醒应用进程
- Native 层集成:与 Native 层 Looper 统一使用 epoll
- 高性能:epoll 在高并发场景下性能优于 wait
问题6:Handler 的消息分发优先级是什么?
标准答案(30秒): Handler 处理消息有三级优先级:最高优先级是 Message 自带的 callback,即通过 post 方法传入的 Runnable;第二优先级是 Handler 构造时传入的 Callback 接口,如果 handleMessage 返回 true 会拦截后续处理;最低优先级是 Handler 子类重写的 handleMessage 方法。这种设计提供了灵活的消息处理机制,既能通过 post 简化单次任务,又能通过 Callback 实现拦截逻辑,还能通过继承实现复杂处理。
源码验证:
// Handler.java (Android 12)
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg); // 优先级1
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return; // 优先级2,返回 true 则拦截
}
}
handleMessage(msg); // 优先级3
}
}
实战应用:
// 优先级1:post 方式
handler.post(() -> {
Log.d("Priority", "优先级1:msg.callback");
});
// 优先级2:Callback 拦截
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.d("Priority", "优先级2:mCallback");
// 返回 true 拦截,不会执行优先级3
// 返回 false 继续执行优先级3
return true;
}
});
// 优先级3:子类重写
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d("Priority", "优先级3:handleMessage");
}
};
追问:为什么 mCallback 返回 true 可以拦截 handleMessage?
答案: 这是一种责任链模式的变体。通过返回值控制是否继续传递消息,提供了灵活的扩展机制。典型应用场景:
- 统一拦截处理:例如全局日志记录、埋点统计
- 条件处理:根据消息类型决定是否继续处理
- 测试 Mock:测试时替换处理逻辑
// 示例:统一日志拦截
Handler handler = new Handler(msg -> {
Log.d("Handler", "收到消息: " + msg.what);
return false; // 返回 false,继续执行子类的 handleMessage
}) {
@Override
public void handleMessage(Message msg) {
// 实际处理逻辑
}
};
七、总结与展望
7.1 核心要点回顾
四大核心类职责:
- Handler:发送消息、处理消息
- Message:消息载体、对象池复用
- MessageQueue:消息队列、阻塞唤醒
- Looper:消息循环、线程隔离
关键机制:
- 线程切换:Handler 绑定 Looper,消息在 Looper 线程执行
- 阻塞唤醒:epoll 机制,无消息时阻塞,有消息时唤醒
- 消息复用:Message 对象池,避免频繁创建
- 内存泄漏:静态内部类 + 弱引用 + 及时移除消息
7.2 进阶学习方向
深入理解本文内容后,建议学习:
- Handler 使用方式:sendMessage、post、postDelayed 等 API 详解
- Handler 工作流程:完整源码分析,消息发送到处理的全流程
- MessageQueue 详解:同步屏障、IdleHandler、Native 层实现
- Looper 机制:ThreadLocal 原理、epoll 机制、主线程 Looper 特殊性
- HandlerThread 与 IntentService:线程封装与应用
7.3 实战建议
日常开发注意事项:
- 优先使用 Message.obtain():避免频繁创建对象
- 及时移除消息:Activity 销毁时调用
removeCallbacksAndMessages(null) - 避免内存泄漏:使用静态内部类或在 onDestroy 清理
- 选择合适的线程:耗时操作在子线程,UI 操作在主线程
- 使用 HandlerThread:需要子线程 Looper 时优先使用封装好的 HandlerThread
面试准备:
- 熟练画出四大核心类关系图
- 能口述完整的消息处理流程
- 理解 epoll 阻塞唤醒机制
- 掌握内存泄漏原因及解决方案
- 了解 ThreadLocal、消息池等细节实现
参考资料
源码路径(基于 Android 12):
frameworks/base/core/java/android/os/Handler.javaframeworks/base/core/java/android/os/Message.javaframeworks/base/core/java/android/os/MessageQueue.javaframeworks/base/core/java/android/os/Looper.javaframeworks/base/core/java/android/os/HandlerThread.javaframeworks/base/core/jni/android_os_MessageQueue.cpp
官方文档: