Android Handler 机制完全解析:从源码到内存泄漏,一篇就够了
本文将从 Looper → MessageQueue → Handler → 内存泄漏 这条主线出发,配合 AOSP 关键源码和注释,把 Handler 机制讲清楚。读完这篇,面试官再问 Handler,你可以从 ActivityThread 的第一行代码开始讲起。
一、为什么需要 Handler?
Android 的主线程(UI 线程)不允许执行耗时操作 (否则 ANR),同时非主线程不能直接更新 UI。Handler 就是连接"后台线程"和"主线程"的桥梁:子线程完成任务后,通过 Handler 把结果抛回主线程执行。
但这只是表象。Handler 的本质是 跨线程通信 + 任务调度 ------它依赖于 Looper + MessageQueue 构成的消息循环。
二、架构总览:四大组件
| 组件 | 职责 | 数量 |
|---|---|---|
| Looper | 消息循环,不断从队列取消息 | 每个线程最多 1 个 |
| MessageQueue | 消息队列,按时间排序存储 Message | 与 Looper 一一对应 |
| Handler | 发送 & 处理消息 | 可绑定同一 Looper 的多个 |
| Message | 消息载体,携带数据 + 处理目标 | 可复用(对象池) |
一句话流程:
Handler.sendMessage() → MessageQueue.enqueueMessage() → Looper.loop() → MessageQueue.next() → Handler.dispatchMessage()
三、Looper:消息循环的发动机
3.1 谁创建了主线程的 Looper?
很多人知道"主线程默认有 Looper",但很少有人看过 ActivityThread 的源码:
java
// android/app/ActivityThread.java
public static void main(String[] args) {
// ...
// ① 为主线程创建 Looper 和 MessageQueue
Looper.prepareMainLooper();
// ② 开始消息循环 ------ 主线程就在这里"卡住"了
Looper.loop();
// ③ loop() 返回意味着程序退出,正常情况下永远不会执行到这里
throw new RuntimeException("Main thread loop unexpectedly exited");
}
注意第 ③ 行------throw new RuntimeException。是的,如果 loop() 返回了,应用直接崩溃。因为主线程一旦退出消息循环,整个应用就废了。
3.2 Looper.prepare():ThreadLocal 的经典应用
prepareMainLooper() 最终调用的是 prepare(),而这里的核心就是 ThreadLocal:
java
// android/os/Looper.java
// ★ ThreadLocal:每个线程独立存储自己的 Looper 实例
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
// 一个线程只能有一个 Looper ------ 重复 prepare 直接抛异常
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 创建 Looper(内部会创建 MessageQueue),塞进当前线程的 ThreadLocal
sThreadLocal.set(new Looper(quitAllowed));
}
// 供其他线程调用
public static void prepare() {
prepare(true); // 子线程允许退出
}
// 主线程专用
public static void prepareMainLooper() {
prepare(false); // 主线程不允许退出 MessageQueue
// ★ 将主线程的 Looper 额外保存一份到 sMainLooper
// 供 Looper.getMainLooper() 静态方法随时获取(不依赖 ThreadLocal)
// 这样其他线程要往主线程发消息时,不需要自己在主线程里取
setMainLooper(myLooper());
}
// Looper 中的 sMainLooper 字段
private static Looper sMainLooper; // guarded by Looper.class
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
// setMainLooper 的实现(包内可见)
// 其实就是把主线程的 Looper 赋值给 sMainLooper 静态变量
private static void setMainLooper(Looper looper) {
synchronized (Looper.class) {
sMainLooper = looper;
}
}
关键理解:
ThreadLocal<Looper>是一个线程局部变量,每个线程存取的都是自己的副本- 调用
Looper.prepare()的线程,内部new Looper()会创建MessageQueue,然后通过sThreadLocal.set()存入当前线程 - 之后调用
Looper.myLooper()就是从sThreadLocal.get()取出,这就是 Handler 绑定 Looper 的底层原理
java
public static @Nullable Looper myLooper() {
return sThreadLocal.get(); // 从当前线程取出之前存进去的 Looper
}
3.2.1 ThreadLocal 内部结构:一张图看懂
Looper.sThreadLocal.set(looper) 看起来只是一行代码,但底层结构远比想象中复杂。它的内部数据结构是这样的:
vbnet
┌────────────────────────────────────────────────────────────┐
│ Thread(线程对象) │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ threadLocals (ThreadLocal.ThreadLocalMap) │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────┐ │ │
│ │ │ Entry[] table │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │
│ │ │ │ Entry │ │ Entry │ │ │ │
│ │ │ │ key: ────────┐ │ │ key: ────────┐ │ │ │ │
│ │ │ │ [WeakRef] │ │ │ [WeakRef] │ │ │ │ │
│ │ │ │ ThreadLocal │ │ │ ThreadLocal │ │ │ │ │
│ │ │ │ <Looper> │ │ │ <String> │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ value: ❤️ │ │ │ value: 🧵 │ │ │ │ │
│ │ │ │ Looper │ │ │ "hello" │ │ │ │ │
│ │ │ │ (mQueue, │ │ │ │ │ │ │ │
│ │ │ │ mThread) │ │ │ │ │ │ │ │
│ │ │ └───────────────┘ └──────────────────┘ │ │ │
│ │ └────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
▲ sThreadLocal.set(looper) ▲ otherTl.set("hello")
│ │
Looper.sThreadLocal 其他 ThreadLocal
(static final) 实例
关键要点:
-
Thread 内部有一个
ThreadLocalMap字段(名叫threadLocals) ,这是数据真正的存储容器。ThreadLocal本身不存数据,它只是查找时的 key。 -
ThreadLocalMap内部维护一个Entry[]数组 ,采用开放地址法(不是拉链法)解决哈希冲突。 -
每个
Entry的key是ThreadLocal实例(弱引用),value是存入的值。key用WeakReference包装:static class Entry extends WeakReference<ThreadLocal<?>>- 这意味着:如果
ThreadLocal对象自身不再被强引用(比如sThreadLocal被置 null),GC 时可以回收 Entry 的 key,但 value 仍存在强引用,这就是 ThreadLocal 内存泄漏的根因。
-
取值流程 :
sThreadLocal.get()→ 获取当前线程的threadLocals→ 以sThreadLocal为 key 在Entry[]中查找 → 返回对应的value(Looper 实例)。 -
主线程的特殊性 :
sThreadLocal是static final,类加载后永远不会被 GC,所以主线程 Looper 在进程存活期间永远不会被回收。
java
// Thread.java 内部的字段
ThreadLocal.ThreadLocalMap threadLocals = null;
// ThreadLocal.set() 的简化逻辑
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // t.threadLocals
if (map != null) {
map.set(this, value); // this 就是 ThreadLocal 实例自身
} else {
createMap(t, value);
}
}
// ThreadLocal.get() 的简化逻辑
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
return setInitialValue(); // 没找到就设初始值
}
总结一句:
sThreadLocal.set(looper)并不是往ThreadLocal对象里塞东西,而是往当前线程的 ThreadLocalMap 里插入了一条Entry(key = sThreadLocal, value = looper)。取的时候从当前线程 的 map 里以sThreadLocal查。这就是**"线程隔离"的精髓**。
3.3 Looper 构造方法:创建 MessageQueue
java
private Looper(boolean quitAllowed) {
// Looper 内部持有一个 MessageQueue ------ 一对一关系
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
3.4 Looper.loop():永不终止的 for 循环
java
// android/os/Looper.java
public static void loop() {
final Looper me = myLooper();
if (me == null) {
// 没有 prepare 就 loop ------ 直接崩
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// ★ 确保当前线程的 IPC 监听正常工作(和 Binder 有关)
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// ★ 死循环 ------ 不断从 MessageQueue 取消息
for (;;) {
// ① 从队列取消息(可能阻塞)
Message msg = queue.next(); // ★ 关键方法,下文详析
if (msg == null) {
// next() 返回 null 表示队列已退出,结束循环
return;
}
// ② 分发消息给 Handler
msg.target.dispatchMessage(msg); // ★ 关键分发,后文详析
// ③ 回收 Message 回对象池
msg.recycleUnchecked();
}
}
面试高频题:为什么 Looper.loop() 不会导致 ANR?
答:
loop()在queue.next()处阻塞。当队列没有消息时,next()通过nativePollOnce()进入内核态的 epoll 休眠 ,不消耗 CPU。有新消息入队时,nativeWake()唤醒。所以主线程一直在 loop 里"等着",但大部分时间是在休眠------直到有事件(点击、触摸、刷新)需要处理。
四、MessageQueue:消息的优先级队列
4.1 enqueueMessage():消息入队
当你调用 handler.sendMessage(msg),最终会走到这里:
java
// android/os/MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
// ● Handler 不能为空
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// ● 消息不能正在使用中
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
// 队列已退出,回收消息
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when; // ★ 这个消息应该"什么时候"被执行(毫秒时间戳)
Message p = mMessages; // 链表头
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// ① 队列空,或者这条消息是"紧急"的(when=0 或比头还早)
// → 插到头部
msg.next = p;
mMessages = msg;
needWake = mBlocked; // 如果正在休眠,需要唤醒
} else {
// ② 按时间顺序插入链表(升序)
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false; // 已有异步消息等着了,不需要额外唤醒
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// ★ 如果需要,唤醒正在 next() 中 nativePollOnce 阻塞的线程
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
关键点:
- MessageQueue 内部是单向链表 ,按
when(执行时间戳)升序排列 - 插入如果影响头节点,且队列正在阻塞等待,就调用
nativeWake()唤醒 Looper 线程重新取消息 when == 0表示立即执行,总是插到头部
4.2 next():消息出队
java
// android/os/MessageQueue.java
Message next() {
final long ptr = mPtr;
if (ptr == 0) return null; // 队列已销毁
int pendingIdleHandlerCount = -1; // -1 表示还没初始化
int nextPollTimeoutMillis = 0; // 阻塞时间
for (;;) {
if (nextPollTimeoutMillis != 0) {
// 调用 native 层的 epoll_wait,释放 CPU 等待
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis); // ★ 阻塞!
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// ★★★ 同步屏障处理 ★★★
if (msg != null && msg.target == null) {
// 遇到屏障消息(target == null),跳过所有同步消息,
// 找到第一个异步消息(isAsynchronous() == true)优先执行
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 消息时间还没到 → 计算还需等待多久
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// ★ 时间到了 → 取出这个消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next; // 从链表中移除
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg; // ← 返回给 loop() 分发
}
} else {
// 队列空 → 一直等,直到被 nativeWake 唤醒
nextPollTimeoutMillis = -1;
}
// ★★★ IdleHandler 处理 ★★★
// 只有在队列空或当前消息还没到时间时,才执行 IdleHandler
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue; // 没有 IdleHandler → 继续阻塞等待
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// ★ 执行 IdleHandler(在 synchronized 块外执行,避免死锁)
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release reference
boolean keep = false;
try {
keep = idler.queueIdle(); // ★ 执行空闲回调
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
// 返回 false → 执行一次后自动移除
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 重置 IdleHandler 计数器,回到外层循环取消息
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0; // 立刻再检查一次是否有消息可用
}
}
五、Handler:消息的发送与处理
5.1 创建 Handler:绑定哪个 Looper?
java
// android/os/Handler.java
// ① 默认构造 ------ 绑定当前线程的 Looper
public Handler() {
this(null, false);
}
// ② 指定 Looper
public Handler(Looper looper) {
this(looper, null, false);
}
// ③ 最底层构造
public Handler(@Nullable Callback callback, boolean async) {
// ★ 从 ThreadLocal 取当前线程的 Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
// 当前线程没有调用 Looper.prepare() → 崩溃
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; // 绑定 MessageQueue
mCallback = callback; // 回调接口
mAsynchronous = async; // 是否默认异步消息
}
5.2 发送消息:sendMessage → enqueueMessage
java
// Handler.java --- 发送消息的完整调用链
// ① 立即发送
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
// ② 延迟发送
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) delayMillis = 0;
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// ③ 所有 sendXxx 最终都汇聚到这里
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
return false;
}
// ★ 把 Handler 自身设为 Message 的 target
// 后续 dispatchMessage 时通过 msg.target 找回对应的 Handler
return enqueueMessage(queue, msg, uptimeMillis);
}
// ④ 入队
private boolean enqueueMessage(@NonNull MessageQueue queue,
@NonNull Message msg, long uptimeMillis) {
msg.target = this; // ★ 关键:Message 持有 Handler 引用!
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis); // 交给 MessageQueue
}
关键理解: msg.target = this ------ Message 持有 Handler 的强引用 。这一行是 Handler 内存泄漏链上的重要一环,后面会详细分析。
5.3 分发消息:dispatchMessage
当 Looper 取出消息后,调用 msg.target.dispatchMessage(msg):
java
// Handler.java
public void dispatchMessage(@NonNull Message msg) {
// ① 优先:Message 自己的 callback(Handler.post(runnable) 方式)
if (msg.callback != null) {
handleCallback(msg);
return;
}
// ② 其次:Handler 构造时传入的 Callback 接口
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return; // 返回 true → 不再执行 handleMessage
}
}
// ③ 最后:子类重写的 handleMessage
handleMessage(msg);
}
// 三种回调优先级:
// msg.callback > mCallback > handleMessage()
private static void handleCallback(Message message) {
message.callback.run(); // Runnable 的 run()
}
5.4 post(Runnable) vs sendMessage(Message)
java
// Handler.java
public final boolean post(@NonNull Runnable r) {
// 内部还是 sendMessageDelayed,只不过把 Runnable 塞进 Message.callback
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain(); // 从对象池拿
m.callback = r; // ★ callback 就是 Runnable
return m;
}
两者本质相同。post(Runnable) 只是语法糖,把 Runnable 封装成了 Message。
六、同步屏障(Sync Barrier):让异步消息插队
6.1 为什么需要同步屏障?
Android 的 UI 渲染依赖 VSync(垂直同步信号) 。当屏幕需要刷新时,如果消息队列前面排了十几条普通消息,渲染动作就必须排队------这会导致掉帧。
同步屏障就是用来解决这个问题的:让异步的渲染消息绕过同步消息,插队执行。
6.2 屏障消息长什么样?怎么用?
java
// MessageQueue.java
// ★ 插入屏障消息
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++; // 唯一标识
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
// ★★★ 屏障消息的 target == null ★★★
// 正常消息的 target 指向 Handler,但屏障消息不指向任何 Handler
// next() 通过判断 msg.target == null 来识别屏障
// msg.target = null; ← Message.obtain() 默认就是 null
// 插到链表头部
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) {
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
6.3 移除屏障
java
// MessageQueue.java
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
// 找到 token 匹配的屏障消息
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) throw new IllegalStateException("...");
// 移除屏障
if (prev != null) {
prev.next = p.next;
} else {
mMessages = p.next;
}
// 如果移除了头节点而队列被阻塞,需要唤醒
final boolean needWake;
if (prev == null && p.next != null) {
needWake = mMessages.target == null; // 下一个消息是不是也是屏障
} else {
needWake = false;
}
p.recycleUnchecked();
if (needWake) {
nativeWake(mPtr);
}
}
}
6.4 屏障消息在 next() 中的处理逻辑
回顾 next() 中的关键代码片段:
java
// ★ 遇到屏障(target == null),跳过所有同步消息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
// ↑ 一直遍历,直到找到第一个异步消息
}
流程总结:
ini
消息队列: [屏障] → [普通消息1] → [普通消息2] → [异步消息★] → [普通消息3]
next() 发现头是屏障:
→ 跳过普通消息1、普通消息2
→ 取出异步消息★ 返回给 Looper 分发
→ 屏障继续留在队列中,直到 removeSyncBarrier() 移除
知识点:
ViewRootImpl.scheduleTraversals()内部就是通过屏障 + 异步消息来实现 VSync 同步渲染的。每次 UI 刷新,先 post 屏障,再发异步渲染消息。
七、IdleHandler:空闲时机的利用
7.1 什么是 IdleHandler?
当 MessageQueue 当前没有消息 或消息还没到执行时间时,会执行注册的 IdleHandler。
java
// MessageQueue.java --- IdleHandler 接口
public static interface IdleHandler {
/**
* 当消息队列空闲时调用
* @return true → 保留,下次空闲继续调用
* false → 执行一次后自动移除
*/
boolean queueIdle();
}
7.2 注册与移除
java
// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) throw new NullPointerException("handler == null");
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
7.3 典型使用场景
java
// ① 延迟加载(等界面渲染完成后再加载非关键资源)
Looper.myQueue().addIdleHandler(() -> {
// 界面已渲染完成,在这里加载图片缓存
ImageLoader.getInstance().warmUp();
return false; // 执行一次就够了
});
// ② 用 IdleHandler 替代 Handler.postDelayed 做非紧急任务
// 性能更好,因为不需要同时维护一个定时器
7.4 IdleHandler 的触发时机(回顾 next())
在 next() 方法中,只有在以下情况才会触发 IdleHandler:
java
// 条件:队列空,或者第一个消息还没到时间
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
注意:
- IdleHandler 不是在每次循环都执行,只在队列空 或当前无到期消息时执行
- 如果队列一直有消息,IdleHandler 永远不会触发
- 不要在 IdleHandler 里做耗时操作------它会延迟下一个消息的处理
八、消息复用:Message.obtain() 与对象池
java
// Message.java
// ★ 对象池:单链表,最多 50 个
private static Message sPool;
private static int sPoolSize;
private static final int MAX_POOL_SIZE = 50;
// 从对象池取,避免 new
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // 清除使用标志
sPoolSize--;
return m;
}
}
return new Message(); // 池空才 new
}
// 回收回池
void recycleUnchecked() {
// 清空所有字段
flags = FLAG_IN_USE;
what = 0; arg1 = 0; arg2 = 0;
obj = null; replyTo = null; sendingUid = -1; when = 0;
target = null; callback = null; data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
最佳实践: 始终用
Message.obtain()或Handler.obtainMessage()获取 Message,避免new Message()。差一个对象通常没啥,但在 ListView/RecyclerView 滑动场景下可能瞬间创建数千个 Message。
九、内存泄漏分析与解决
9.1 经典泄漏场景
java
public class MainActivity extends AppCompatActivity {
// ★ 非静态内部类,隐式持有 Activity 引用
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
// 假设这里更新 UI
textView.setText("...");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 发送一个延迟 10 分钟的消息
mHandler.sendMessageDelayed(Message.obtain(), 10 * 60 * 1000);
}
}
问题: 如果用户在消息触发前退出 Activity,这条延时消息还在 MessageQueue 里。引用链如下:
csharp
Activity(想要回收)
↑ 隐式持有(非静态内部类)
Handler
↑ 持有(msg.target = this)
Message
↑ 持有(链表引用)
MessageQueue
↑ 持有
Looper(主线程的 Looper 永远不会被回收,因为它在 ActivityThread.main() 里)
↑ 持有(sThreadLocal.set())
ThreadLocal(static 变量,类加载到卸载一直存在)
引用链完整路径:
markdown
静态区:ThreadLocal.sThreadLocal
→ Looper(主线程)
→ MessageQueue
→ Message(链表节点)
→ Handler(msg.target)
→ Activity(非静态内部类持有外部的引用)
结论: Activity 无法被 GC 回收,内存泄漏。
9.2 修复方案
方案一:静态内部类 + 弱引用(推荐)
java
public class MainActivity extends AppCompatActivity {
// ★ 静态内部类
private static class SafeHandler extends Handler {
// ★ 弱引用持有 Activity
private final WeakReference<MainActivity> mActivityRef;
SafeHandler(MainActivity activity) {
super(Looper.getMainLooper());
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
MainActivity activity = mActivityRef.get();
if (activity == null) return; // Activity 已被回收
// 安全更新 UI
activity.textView.setText("...");
}
}
private final SafeHandler mHandler = new SafeHandler(this);
}
方案二:Activity/Fragment 销毁时移除所有未处理消息
java
@Override
protected void onDestroy() {
// ★ 移除该 Handler 所有未处理的 Message 和 Runnable
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
参数传 null 表示移除所有与 mHandler 关联的消息。
9.3 子线程 Handler 为什么不会泄漏?
java
new Thread(() -> {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}).start();
这个 Handler 不会泄漏 Activity,因为如果 Activity 和这个子线程没有引用关系------但如果 Handler 是匿名内部类,情况一样危险。
更关键的是:子线程执行完任务后应该调用 Looper.quit() 退出循环,否则线程永远不会结束,一样泄漏线程资源。
9.4 更深层:主线程 Looper 为什么永不释放
java
// ActivityThread.main()
Looper.prepareMainLooper();
Looper.loop(); // 死循环,永不返回
因为 sThreadLocal 是 static final 的类变量,它持有的主线程 Looper 引用链是:
erlang
类静态区 → sThreadLocal → ThreadLocalMap.Entry → Looper → MessageQueue → ...
只要类加载器活着(应用进程活着),主线程 Looper 这个引用链就不会断,所以任何通过 Message.target → Handler → 外部类 这条线挂上去的对象都永久可达。
这就是 Handler 内存泄漏比一般泄漏更隐蔽、更致命的地方------泄漏的不是暂时的,而是永久存在的。
十、Handler 机制全景图
scss
┌─────────────────────────────────────────────────────────────┐
│ ActivityThread.main() │
│ │
│ ① Looper.prepareMainLooper() │
│ └─ sThreadLocal.set(new Looper()) ← ThreadLocal 存储 │
│ └─ new MessageQueue() ← 创建消息队列 │
│ │
│ ② Looper.loop() ← 启动消息循环(永不返回) │
│ └─ for (;;) { │
│ msg = queue.next(); ← 取消息(可能阻塞) │
│ ├─ nativePollOnce() ← epoll 休眠 │
│ ├─ 同步屏障检测 ← 跳过同步消息 │
│ ├─ IdleHandler 执行 ← 空闲时执行 │
│ └─ return msg │
│ │
│ msg.target.dispatchMessage(msg) ← 分发 │
│ ├─ msg.callback.run() ← post(Runnable) │
│ ├─ mCallback.handleMessage() ← Callback 接口 │
│ └─ handleMessage() ← 子类重写 │
│ │
│ msg.recycleUnchecked() ← 回收到对象池 │
│ } │
└─────────────────────────────────────────────────────────────┘
子线程 主线程
┌──────────────┐ ┌──────────────────────┐
│ Handler │──sendMessage──→│ MessageQueue │
│ .sendMessage()│ │ [屏障] [Msg1] [★] │
│ .post() │ └──────────┬───────────┘
└──────────────┘ │
▼
Looper.loop()
│
▼
Handler.dispatchMessage()
│
▼
更新 UI
十一、面试常见追问
Q1:一个线程可以有几个 Handler?几个 Looper?
- Looper:最多 1 个(
prepare()重复调用抛异常) - Handler:可以有多个,全部绑定同一个 Looper 及其 MessageQueue
- 主线程不需要自己
prepare,ActivityThread 已经调好了
Q2:Handler 的 post 和 sendMessage 有什么区别?
没有本质区别。post(Runnable) 内部还是 sendMessageDelayed(),只是把 Runnable 设为 msg.callback,在 dispatchMessage() 时直接 callback.run()。优先级上 msg.callback 高于 handleMessage()。
Q3:Looper.loop() 为什么不会卡死主线程?
因为 queue.next() 在无消息时通过 nativePollOnce(ptr, timeout) 进入 epoll 休眠,不消耗 CPU。有新消息入队时,nativeWake() 唤醒。这段休眠完全由 Linux 内核 epoll 机制管理,高效且节能。
有人说"那 MessageQueue 没消息不就一直卡在 next() 里?"------对,但 Android 的事件驱动模型就是这样。永远不返回,下次有新消息(点击、触摸、刷新)时唤醒就行。这就是"事件循环"的本质。
Q4:子线程 Looper 和主线程 Looper 有什么区别?
| 对比项 | 主线程 Looper | 子线程 Looper |
|---|---|---|
| prepare | prepareMainLooper() |
prepare() |
| 能否 quit | ❌ quitAllowed = false |
✅ quitAllowed = true |
| 创建者 | ActivityThread.main() | 开发者 |
| 生命周期 | 进程存活期 | 手动管理 |
Q5:同步屏障的原理能不能画一下?
scss
同步消息排在前 → postSyncBarrier → 屏障消息插到头部
[屏障] → [同步A] → [同步B] → [异步C] → [同步D]
next() 检测到头部 target==null(屏障)
→ 跳跳跳跳过同步A、同步B
→ 取出异步C
屏障一直留着,直到 removeSyncBarrier()
十二、总结
- Looper 是驱动,通过
ThreadLocal保证每个线程只有一个,由ActivityThread.main()为 UI 线程创建 - MessageQueue 是容器,内部用单向链表按时间排序,通过
nativePollOnce/nativeWake(epoll)实现高效阻塞等待 - Handler 是入口/出口,发消息时设
msg.target = this,取消息时通过msg.target.dispatchMessage()回调 - 同步屏障 让异步渲染消息插队,保证 UI 不掉帧
- IdleHandler 利用空闲时间执行非紧急任务,性能优于
postDelayed - 内存泄漏 的根本原因是 ThreadLocal 的静态生命周期 + 非静态内部类的隐式引用,解决方案是静态内部类 + 弱引用 + 及时移除消息
本文源码基于 Android 14(API 34),核心部分与早年版本基本一致。如有错漏,欢迎指正。