返回 Android
Android
17 分钟阅读

Android Handler 机制全解:从源码看懂消息循环

从 Handler、Looper、MessageQueue、Message 四件套出发,结合 ActivityThread 启动链路与 nativePollOnce 底层实现,完整讲清 Android 主线程消息循环的设计与源码细节。

Handler 是 Android 里最基础、也最容易被误读的机制之一。很多人知道「Handler 发消息、Looper 循环取消息」,却不清楚:主线程为什么不会因 for(;;) 卡死?nativePollOnce() 到底在等什么?post(Runnable)sendMessage() 在源码里走同一条路吗?

这篇从源码出发,把 Handler 四件套(Handler / Looper / MessageQueue / Message)串成一条完整链路。


一、整体架构:四件套如何协作

┌─────────────┐    enqueueMessage     ┌──────────────────┐
│   Handler   │ ────────────────────► │  MessageQueue    │
│  (生产者/消费者) │                     │  (单链表优先级队列)  │
└─────────────┘                       └────────┬─────────┘
       ▲                                       │ next()
       │ dispatchMessage                       ▼
       │                               ┌──────────────────┐
       └───────────────────────────────│     Looper       │
                                       │  for(;;) 事件循环  │
                                       └──────────────────┘
组件核心职责关键源码入口
Message消息载体;what / obj / callback / target 等字段Message.obtain()recycleUnchecked()
MessageQueuewhen 排序入队;next() 阻塞取队头enqueueMessage()next()nativePollOnce()
Looper持有一个 MessageQueueloop() 死循环分发prepare()loop()ThreadLocal
Handler绑定 Looper;负责发送与处理消息sendMessage()post()dispatchMessage()

一个线程最多一个 Looper,一个 Looper 对应一个 MessageQueue,可以有多个 Handler 绑定到同一个 Looper(它们共享同一条消息队列,由 Looper 串行分发)。


二、主线程 Looper 从哪来:ActivityThread 启动链

应用进程启动后,系统通过 ActivityThread.main() 进入主线程。简化后的关键路径:

// frameworks/base/core/java/android/app/ActivityThread.java(简化)
public static void main(String[] args) {
    Looper.prepareMainLooper();          // ① 创建主线程 Looper
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);      // ② 绑定 Application、AMS 等
    if (thread.mInstrumentation != null) {
        thread.mInstrumentation.callApplicationOnCreate(...);
    }
    Looper.loop();                       // ③ 进入消息循环,永不返回(正常情况)
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

prepareMainLooper() 与普通的 prepare() 区别:

// frameworks/base/core/java/android/os/Looper.java(简化)
public static void prepareMainLooper() {
    prepare(false);                      // allowQuit = false,主 Looper 不可 quit
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}
 
public static void prepare() {
    prepare(true);                       // 子线程 Looper 允许 quit
}
 
private static void prepare(boolean allowQuit) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(allowQuit));
}

因此:

  • 主线程在 ActivityThread.main()自动有了 Looper,无需手动 prepare()
  • new Handler()new Handler(Looper.getMainLooper()) 发到主线程的消息,最终都由这条 Looper.loop() 消费
  • loop() 意外返回,最后一行会直接抛异常——主线程事件循环退出 = 致命错误

主线程启动时序:


三、Looper 与 ThreadLocal:为什么 Handler 必须绑定 Looper

3.1 ThreadLocal 保存「当前线程的 Looper」

// Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
 
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

Handler 构造时会检查当前线程是否有 Looper:

// Handler.java(简化)
public Handler(@Nullable Callback callback, boolean async) {
    // ...
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
}

这就是为什么子线程直接 new Handler() 会崩溃——没有先 Looper.prepare()

3.2 子线程正确使用 Handler 的模板

new Thread(() -> {
    Looper.prepare();                    // 创建 Looper 并放入 ThreadLocal
    Handler handler = new Handler();
    handler.post(() -> { /* 在此 Looper 线程执行 */ });
    Looper.loop();                       // 阻塞在此,开始消费消息
}).start();

Looper.loop() 正常永不返回;调用 Looper.quit() / quitSafely() 后,next() 返回 null,循环退出。

3.3 loop():事件循环的核心

// Looper.java(AOSP 简化,保留关键逻辑)
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
 
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    final int thresholdOverride =
        me.mSlowDeliveryThresholdMs != INVALID_THRESHOLD
            ? (int) me.mSlowDeliveryThresholdMs : -1;
 
    for (;;) {
        Message msg = queue.next();      // 可能阻塞
        if (msg == null) {
            return;                      // quit 时退出
        }
 
        // 慢消息日志(调试用途)
        if (thresholdOverride >= 0) {
            // logIfSlow(...)
        }
 
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (thresholdOverride >= 0) {
                // logIfSlow(...)
            }
        }
        Binder.clearCallingIdentity();
    }
}

四、Message:对象池、字段含义与回收

4.1 字段一览

// Message.java(核心字段)
public final class Message {
    public int what;
    public int arg1;
    public int arg2;
    public Object obj;
    Handler target;          // 由 Handler.enqueueMessage 赋值
    Runnable callback;       // post(Runnable) 时设置
    long when;               // 执行时间点(uptimeMillis)
 
    // 链表指针,MessageQueue 内部使用
    Message next;
    // 对象池
    private static Message sPool;
    private static int sPoolSize;
    // ...
}
字段含义
what整型消息标识,配合 handleMessage()
arg1 / arg2轻量参数,避免为简单数据再装箱 obj
obj任意对象载荷
callbackpost(Runnable) 时非空,优先于 handleMessage 执行
target处理此消息的 Handler
when绝对时间戳(SystemClock.uptimeMillis() 基准),决定队列顺序

4.2 对象池:obtain() 与 recycle

频繁创建 Message 会产生 GC 压力,因此框架用链表对象池复用:

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

消息处理完毕后,Looper.loop() 分发结束会调用 msg.recycleUnchecked() 把 Message 放回池中。不要长期持有已入队的 Message 引用——回收后字段会被清空,且可能已被复用。


五、Handler 发送消息:从 API 到入队

5.1 post 与 sendMessage 殊途同归

// Handler.java
public final boolean post(@NonNull Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}
 
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
 
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
 
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    // ...
    return enqueueMessage(msg, uptimeMillis);
}
 
private boolean enqueueMessage(Message msg, long when) {
    if (msg.target != null && msg.target.getLooper() != mLooper) {
        throw new IllegalArgumentException(
            "Message must be sent to the Handler's Looper!");
    }
    if (msg.target != null) {
        msg.target = this;
    }
    return mQueue.enqueueMessage(msg, when);
}

结论:

  • post(Runnable) = obtain() 一个 Message + 设置 callback + when = now + delay
  • sendMessage(Message) = 设置 what/obj 等 + 同样走 enqueueMessage
  • 最终都进入同一个 MessageQueue

5.2 dispatchMessage:三种处理优先级

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);             // ① post(Runnable) 走这里
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;                  // ② Handler 构造时传入的 Callback
            }
        }
        handleMessage(msg);              // ③ 子类覆写 handleMessage
    }
}
 
private static void handleCallback(Message message) {
    message.callback.run();
}

优先级:msg.callback(Runnable)> Handler.mCallback > handleMessage() 子类实现


六、MessageQueue 源码:入队、出队与 nativePollOnce

6.1 数据结构:按 when 排序的单链表

MessageQueue 不是 java.util.Queue,而是when 升序排列的单链表,队头是最早该执行的消息。

// MessageQueue.java
Message mMessages;   // 队头

6.2 enqueueMessage:插入排序 + 唤醒

boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        if (mQuitting) {
            msg.recycle();
            return false;
        }
 
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // 插到队头
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;         // 若正在 nativePollOnce 阻塞,需要唤醒
        } else {
            // 按 when 找到合适位置插入中间或尾部
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
            }
            msg.next = p;
            prev.next = msg;
        }
 
        if (needWake) {
            nativeWake(mPtr);            // 唤醒 epoll_wait
        }
    }
    return true;
}

要点:

  • when == 0 表示「立即执行」,会插到队头(仅次于同步屏障等特殊消息)
  • 插入时若 Looper 正阻塞在 nativePollOnce(),且新消息比当前等待的更早到期,则 nativeWake() 提前唤醒

6.3 next():阻塞等待的核心

@UnsupportedAppUsage
Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
 
    int pendingIdleHandlerCount = 0;
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
 
        nativePollOnce(ptr, nextPollTimeoutMillis);  // ★ 阻塞点
 
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            // ... 同步屏障逻辑(见第八节)...
 
            if (msg != null) {
                if (now < msg.when) {
                    // 队头消息还没到时间,计算还要睡多久
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 取走队头
                    mBlocked = false;
                    prevMsg = msg;
                    msg = msg.next;
                    prevMsg.next = null;
                    return prevMsg;
                }
            } else {
                nextPollTimeoutMillis = -1;          // 无消息,无限期等待
            }
 
            // IdleHandler 回调(队列空闲时)
            // ...
        }
    }
}

nextPollTimeoutMillis 的取值逻辑:

含义
0不阻塞,立即返回(刚被 nativeWake 唤醒时常见)
> 0阻塞最多 N 毫秒,等待延时消息到期
-1无限期阻塞,直到有新消息入队并 nativeWake

6.4 Native 层:epoll 与唤醒

Java 层的 nativePollOnce / nativeWake 在 JNI 中实现,底层使用 Linux epoll(早期版本为 pipe + epoll):

// frameworks/base/core/jni/android_os_MessageQueue.cpp(概念简化)
void NativeMessageQueue::pollOnce(int timeoutMillis) {
    // ...
    int eventCount = mEpollFd.modifyEvents(...);
    // epoll_wait 阻塞,直到:新消息入队(nativeWake)、定时器到期、fd 事件
    eventCount = mEpollFd.wait(timeoutMillis);
    // ...
}
 
void NativeMessageQueue::wake() {
    mWakeEventFd.write(1);   // 向 eventfd 写入,打断 epoll_wait
}

这就是为什么 for(;;) 不会占满 CPU:线程在 epoll_wait 中睡眠,由内核调度;有新消息时 nativeWake 写入 eventfd,线程被唤醒回到 next() 取消息。


七、阻塞语义:Java 层阻塞 vs Android 层事件驱动

很多人困惑:for(;;) 明明是死循环,为什么主线程还能响应触摸?

视角结论原因
Java / 线程层面同步阻塞nativePollOnce() 让线程在内核态挂起,几乎不占 CPU
Android / 应用层面事件驱动、非忙等有消息就处理,没消息就睡眠,随时可被 nativeWake 唤醒
日常开发语境「阻塞」≈ 主线程执行耗时任务ANR / 掉帧来自 dispatchMessage() 执行太久,不是 nativePollOnce() 的等待

7.1 与 Terminal 程序的同构类比

// Terminal 程序
while (true) {
    char* input = read();      // 阻塞等输入 → nativePollOnce()
    process(input);            // 处理 → dispatchMessage()
}
// Android Looper
for (;;) {
    Message msg = queue.next();          // 阻塞等消息
    msg.target.dispatchMessage(msg);     // 处理消息
}

若主线程没有这个循环:ActivityThread.main() 执行完就返回 → 线程结束 → 进程退出 → App 闪退。Windows 消息循环、iOS RunLoop、Qt exec() 都是同一类设计。

7.2 两种「阻塞」不要搞混

// ✅ 正常:等待期间不占 CPU,新消息可唤醒
// Looper 在 nativePollOnce() 中睡眠
 
// ❌ 危险:dispatchMessage 执行太久
handler.post(() -> {
    Thread.sleep(5000);   // UI 无响应、ANR 风险
});
阻塞类型发生位置后果
nativePollOnce() 等待队列空 / 延时消息未到期正常;线程存活且低功耗
dispatchMessage() 耗时Runnable.run() / handleMessage()掉帧、卡顿、ANR

7.3 事件循环流程


八、进阶机制

8.1 IdleHandler:队列空闲时干活

当 MessageQueue 暂时没有可执行的消息(或需要等待下一批),会遍历 IdleHandler 列表:

public static interface IdleHandler {
    boolean queueIdle();
}

典型用途:在主线程空闲时预加载、批量 flush、延迟非紧急初始化。queueIdle() 返回 false 表示一次性,移除;true 则保留。

8.2 同步屏障(Sync Barrier)

MessageQueue 可以插入 target == null 的特殊屏障消息。屏障存在期间:

  • 同步消息(默认 Message)暂停分发
  • 异步消息Message.setAsynchronous(true))优先处理

Choreographer 利用此机制,让 VSYNC 帧回调(异步消息)优先于普通 UI 消息执行,减少掉帧。postSyncBarrier() / removeSyncBarrier() 成对使用。

// enqueueMessage 里判断异步消息是否需要唤醒
needWake = mBlocked && p.target == null && msg.isAsynchronous();

8.3 延时消息与定时精度

延时并非独立 Timer 线程,而是靠 next() 计算 nextPollTimeoutMillis = msg.when - now,在 nativePollOnce睡眠到指定时间或被新消息唤醒。精度受系统调度、主线程繁忙程度影响——主线程若长期被同步消息占满,延时消息会排队滞后


九、内存泄漏:Message 引用链

9.1 泄漏成因

GC Root → MessageQueue → Message(延时未处理)
                              ↓
                          Handler(非静态内部类)
                              ↓
                          Activity(已 finish,仍被引用)

非静态内部类 Handler 隐式持有外部 Activity 引用。若 postDelayed(..., 60_000) 后 Activity 已销毁,Message 仍在队列中 → Activity 无法回收。

9.2 修法

// ① 静态内部类 + 弱引用
private static class SafeHandler extends Handler {
    private final WeakReference<Activity> ref;
    SafeHandler(Activity activity) {
        super(Looper.getMainLooper());
        ref = new WeakReference<>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
        Activity activity = ref.get();
        if (activity == null) return;
        // ...
    }
}
 
// ② onDestroy 清理
@Override
protected void onDestroy() {
    handler.removeCallbacksAndMessages(null);
    super.onDestroy();
}

面试速查表见 Android 面试套路 的 Handler 章节。


十、与异步编程的边界

机制解决什么
Handler / Looper同一线程内消息排队、延时、切换执行时机;保证线程存活
线程池 / Coroutines / RxJava跨线程调度,把 IO 移出主线程

后台线程完成计算后更新 UI,仍需 handler.post { }withContext(Dispatchers.Main)——因为 View 操作必须在主线程 Looper 上执行。异步范式演进见 异步编程思想的演进


十一、常见追问速查

追问源码级答案
一个线程能有几个 Looper?最多一个;prepare()ThreadLocal 已有则抛异常
多个 Handler 共用一个 Looper?可以;它们共享 MessageQueue,由 loop() 串行分发
postsendMessage 区别?都走 enqueueMessagepost 设置 msg.callback
子线程 new Handler() 崩溃?Looper.prepare()myLooper() 为 null
如何退出子线程 Looper?quit() / quitSafely()next() 返回 null → loop() 结束
主线程能 quit Looper 吗?prepareMainLooper()allowQuit=false,正常不可退出
消息执行顺序?同一线程串行when 相同则先入先出
nativePollOnce 做什么?JNI 层 epoll_wait,无消息时阻塞线程,有消息时 nativeWake 唤醒

十二、总结

ActivityThread.main()
    → Looper.prepareMainLooper()     // ThreadLocal 放入主 Looper + MessageQueue
    → ... 创建 Application / Activity ...
    → Looper.loop()
        → for (;;)
            → MessageQueue.next()    // nativePollOnce 阻塞等待
            → Handler.dispatchMessage()
                → callback / handleMessage

Handler 机制的本质:用「无限循环 + 内核级阻塞等待」让主线程活着且低功耗,用「单链表优先级队列 + 串行分发」保证 UI 操作的线程安全与时序可控。

从 Java 看,nativePollOnce() 是同步阻塞;从 Android 看,它是高效的事件驱动;从业务看,真正要担心的是 dispatchMessage() 里的耗时——而不是等待消息时的睡眠。

相关文章

Android
18 分钟
Android面试套路
Android 常见面试题的分层答法、关键词接龙链路与实战追问——从 ANR 到启动模式、从 Handler 到架构选型。
Android
12 分钟
为什么要引入线程调度框架
在Java和Kotlin里面,优雅地实现异步编程模型
Java / Kotlin
12 分钟
异步编程思想的演进:从 Rx 到 Kotlin Coroutines
从 Callback、Future 到 RxJava 与 Kotlin Coroutines——异步编程范式的演进脉络、设计哲学与选型依据