文章

Android Handler 机制与源码深度解析

Android Handler 机制与源码深度解析

Handler 是 Android 中最为核心的异步通信机制。在 Android 的世界里,几乎所有的事件驱动——无论是 UI 的更新、点击事件的回调,还是生命周期的调度,底层都离不开 Handler 机制。

在面试中,Handler 是高频考点。本文将配合源码,从核心组件、工作流程、底层原理到进阶特性,深度解析 Handler 的实现原理。

一、 Handler 机制的四大核心组件

Handler 机制的核心由四个类组成,它们共同协作完成消息的传递和处理:

  1. Message (消息):负责承载数据,是线程间传递的信息载体。
  2. Handler (处理者):负责发送消息 (sendMessage) 以及处理消息 (handleMessage)。
  3. MessageQueue (消息队列):用来存放 Handler 发送过来的 Message。虽然叫队列,但它的内部实现是按时间 (when) 排序的单向链表。
  4. Looper (循环器):是 MessageQueue 的管理者。它以无限循环的形式,不断地从 MessageQueue 中取出 Message,并交由其对应的 Handler 执行。

二、 核心流程源码解析

1. 引擎的启动:Looper.prepare() 与 Looper.loop()

在子线程中使用 Handler 之前,必须先调用 Looper.prepare(),然后调用 Looper.loop()。而在主线程中,系统(在 ActivityThread.main 中)已经自动为我们做好了这两步。

Looper.prepare():保证线程单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Looper.java
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");
    }
    // 将 Looper 存入 ThreadLocal
    sThreadLocal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
    // 创建与当前 Looper 绑定的 MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
  • ThreadLocal 的作用:ThreadLocal 保证了每个线程都有且只有一个自己的 Looper 实例。
  • MessageQueue 的创建:在 Looper 的构造函数中,同时创建了 MessageQueue,因此一个线程 = 一个 Looper = 一个 MessageQueue。

Looper.loop():开启无限循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Looper.java
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;

    // 死循环
    for (;;) {
        // 1. 从消息队列取出消息。这里可能会阻塞!
        Message msg = queue.next(); 
        if (msg == null) {
            // 没有消息表示 MessageQueue 正在退出
            return;
        }

        try {
            // 2. 将消息分发给它的目标 (即发送它的 Handler)
            msg.target.dispatchMessage(msg);
        } catch (Exception exception) {
            throw exception;
        }

        // 3. 回收 Message,放入对象池复用
        msg.recycleUnchecked();
    }
}

Looper.loop() 的核心就是一个死循环,不断调用 queue.next() 取出消息,并通过 msg.target.dispatchMessage(msg) 分发消息。

面试题:主线程的死循环为什么不会导致 ANR? ANR 的本质是“处理某件特定事情超时”(例如按键事件 5 秒无响应),而不是“线程在等事情做”。 当 MessageQueue 中没有消息时,底层会通过 Linux 的 epoll 机制将主线程挂起(休眠),此时线程会释放 CPU 资源,所以不会卡死。当有新消息或底层事件(如触摸屏幕)到来时,会唤醒主线程继续工作。真正的 ANR 是因为在 dispatchMessage (即某次 handleMessage) 中执行了耗时操作,导致后续的消息(如 UI 绘制事件)迟迟得不到处理。

2. 消息的发送与入队:enqueueMessage()

当我们调用 handler.sendMessage(msg) 时,经过层层调用,最终都会走到 MessageQueue.enqueueMessage()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        if (mQuitting) {
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        
        // 1. 如果队列为空,或者新消息的时间最早,将其插入到链表头部
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            // 如果线程正在阻塞,需要唤醒
            needWake = mBlocked;
        } else {
            // 2. 否则,根据时间 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;
        }

        // 3. 唤醒底层的 epoll 等待
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
  • 单链表排序:MessageQueue 内部维护的是一个单向链表 mMessages,新来的消息会根据它的执行时间 (when) 插入到链表的合适位置。
  • 唤醒机制:如果队列原先为空导致线程正在休眠,插入新消息后会调用 nativeWake(mPtr) 唤醒底层,让 queue.next() 恢复执行。

3. 消息的提取与阻塞:MessageQueue.next()

next() 是 MessageQueue 中最复杂的方法,它负责提供下一条需要执行的消息,如果没到时间,就让线程休眠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// MessageQueue.java
Message next() {
    // ...
    int nextPollTimeoutMillis = 0;
    for (;;) {
        // 1. Native 层阻塞操作 (基于 epoll)
        // nextPollTimeoutMillis 为 -1 表示一直等,为 0 表示不等,>0 表示等几毫秒
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            
            // ... (省略同步屏障逻辑)

            if (msg != null) {
                if (now < msg.when) {
                    // 2. 如果当前时间还没到第一条消息的执行时间,计算需要休眠的时间
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 3. 拿到消息!将消息从链表中移除并返回
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 4. 队列里没消息了,无限期等待 (-1)
                nextPollTimeoutMillis = -1;
            }
            
            // ... (省略 IdleHandler 逻辑)
            mBlocked = true;
        }
    }
}

4. 消息的分发:dispatchMessage()

在 Looper.loop() 中拿到消息后,会调用 msg.target.dispatchMessage(msg)。target 其实就是发送这条消息的 Handler 对象本身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Handler.java
public void dispatchMessage(Message msg) {
    // 1. 如果 Message 自身自带 callback (通过 handler.post(Runnable) 发送的)
    if (msg.callback != null) {
        handleCallback(msg); // 直接执行 msg.callback.run()
    } else {
        // 2. 如果 Handler 创建时传入了 mCallback (实现 Handler.Callback 接口)
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return; // 如果 Callback 返回 true,则拦截消息
            }
        }
        // 3. 兜底,回调重写的 handleMessage() 方法
        handleMessage(msg);
    }
}

这里明确了消息处理的优先级:Message.callback > Handler.Callback > handleMessage()。


三、 进阶与常见面试题

1. Handler 内存泄漏的原因及解决方案

原因:在 Java 中,非静态内部类/匿名内部类会隐式地持有外部类的引用。如果我们在 Activity 中以匿名内部类的方式创建 Handler,并且往 MessageQueue 中发送了延迟消息 (postDelayed),那么: 主线程的 MessageQueue -> Message -> Handler -> Activity。 只要消息未被处理,Activity 就无法被垃圾回收,导致内存泄漏。

解决方案:

  1. 静态内部类 + 弱引用:将 Handler 写成静态内部类(不持有外部类引用),并在内部持有 Activity 的 WeakReference。
  2. 生命周期清理:在 Activity 的 onDestroy() 中调用 handler.removeCallbacksAndMessages(null) 清空该 Handler 的所有消息。

2. Message 的复用 (享元模式)

为了避免频繁创建和销毁 Message 造成内存抖动,Message 内部维护了一个单链表的对象池 (sPool),最大容量为 50。

  • 获取:使用 Message.obtain() 而不是 new Message()。它会从池子里取对象,如果池子空了才 new。
  • 回收:Looper.loop() 执行完 dispatchMessage 后,会调用 msg.recycleUnchecked() 将数据清空并放回池子头部。

3. 同步屏障 (Sync Barrier)

在 MessageQueue 中,有一类特殊的消息,它的 target == null,这就是同步屏障。 当调用 MessageQueue.postSyncBarrier() 时,会在队列中插入这样一个没有 target 的消息。 作用:当 MessageQueue.next() 遇到同步屏障时,它会跳过所有的普通消息(同步消息),只去寻找并执行异步消息(Asynchronous Message)。 应用场景:Android 的屏幕刷新 (VSYNC 信号)。ViewRootImpl 在触发 UI 绘制时,会发送一个同步屏障,并将 UI 绘制的任务作为异步消息发送。这样能保证 UI 绘制任务具有最高优先级,不被队列里的其他普通消息阻塞,保证界面的流畅。

4. IdleHandler

MessageQueue.IdleHandler 是一个回调接口,当消息队列空闲时(即队列无消息,或者消息都还没到执行时间准备休眠时)会触发它的 queueIdle() 方法。 应用场景:可以在这里执行一些不那么紧急的初始化操作,或者在 UI 绘制完成后做一些事情,从而加快 Activity 的启动速度,这就是常说的“延迟初始化”。

四、 总结

Android 的 Handler 机制是一套精妙的生产者-消费者模型:

  1. Handler 是生产者,负责将附带执行时间的 Message 塞入 MessageQueue 的对应位置。
  2. Looper 运行在目标线程,是消费者,通过死循环不断在 MessageQueue 中取出到期的消息。
  3. MessageQueue 借助底层 Linux 的 epoll/eventfd 机制实现了高效的“有事做事,无事休眠”,是整套机制的性能基石。
本文由作者按照 CC BY 4.0 进行授权