5、Handler消息机制:Looper、MessageQueue、Handler工作原理,IdleHandler与同步屏障

Handler 机制,说白了就是 Android 里线程间通信的「快递系统」。

我刚开始做 Android 开发时,总觉得这玩意儿有点玄乎。为什么不能在子线程直接更新 UI?为什么非要 post 一个 Runnable?后来踩了几次坑才明白——这背后是一套精心设计的消息循环架构。

5.1 整体架构:三个核心角色

Handler 机制由三个组件构成:

  • Looper——消息循环的发动机,负责不断从队列里取消息
  • MessageQueue——消息的容器,按时间顺序排队
  • Handler——消息的发送者和处理者,你用它发消息,也用它处理消息

它们的关系,我画了张图帮你理解:

Handler 消息机制核心流程 Handler 发送消息 (sendMessage) 处理消息 (handleMessage) post Runnable MessageQueue 消息链表结构 按时间排序 支持同步屏障 Looper 无限循环 调用 next() 取消息 分发到 Handler enqueueMessage next() Looper.loop() → MessageQueue.next() → Handler.dispatchMessage() → 循环

5.2 Looper:消息循环的发动机

每个线程只能有一个 Looper。主线程的 Looper 在 ActivityThread 里已经帮我们准备好了。

你想想看,如果没有 Looper,线程执行完代码就退出了。Looper 的作用就是让线程「活」着,不断从 MessageQueue 里取消息处理。

核心方法:

  • Looper.prepare()——为当前线程创建 Looper(子线程必须手动调用)
  • Looper.loop()——启动消息循环(死循环)
  • Looper.quit()——退出循环
// 子线程使用 Looper 的标准写法
class MyThread extends Thread {
    public Handler handler;
    
    @Override
    public void run() {
        Looper.prepare();  // 创建 Looper 和 MessageQueue
        
        handler = new Handler(Looper.myLooper()) {
            @Override
            public void handleMessage(Message msg) {
                // 处理消息
            }
        };
        
        Looper.loop();  // 启动循环,这行之后代码不会继续执行
    }
}

注意:Looper.loop() 是一个死循环。如果不调用 quit(),线程永远不会结束。我曾经在项目里忘记在 Activity 销毁时退出子线程的 Looper,结果导致内存泄漏——Handler 持有 Activity 引用,Activity 无法被 GC 回收。

5.3 MessageQueue:消息的容器

MessageQueue 内部其实是一个单向链表。消息按执行时间排序,时间越早的越靠前。

它的核心方法是 next()——这是一个阻塞方法。如果队列里没有消息,或者所有消息都还没到时间,next() 就会阻塞住。

// MessageQueue.next() 简化逻辑
Message next() {
    for (;;) {
        // 如果有同步屏障,优先处理异步消息
        if (mMessages != null && mMessages.target == null) {
            // 找到第一个异步消息
        }
        
        if (mMessages != null) {
            if (now < mMessages.when) {
                // 还没到时间,阻塞
                Binder.flushPendingCommands();
                nativePollOnce(ptr, nextPollTimeoutMillis);
            } else {
                // 取出消息并返回
                Message msg = mMessages;
                mMessages = msg.next;
                msg.next = null;
                return msg;
            }
        } else {
            // 队列为空,无限阻塞
            nativePollOnce(ptr, -1);
        }
    }
}

这里有个细节:nativePollOnce() 是 native 层的阻塞调用。它利用 Linux 的 epoll 机制,让线程在没消息时休眠,有消息时被唤醒。这样就不会空转消耗 CPU。

5.4 Handler:消息的收发员

Handler 做的事情其实很简单:

  1. 发送消息——把 Message 塞到 MessageQueue 里
  2. 处理消息——Looper 取出消息后,回调 Handler 的 handleMessage()

Handler 发送消息有几种方式:

// 方式一:sendMessage
handler.sendMessage(msg);
handler.sendMessageDelayed(msg, 1000);
handler.sendEmptyMessage(WHAT_UPDATE);

// 方式二:post Runnable
handler.post(new Runnable() {
    @Override
    public void run() {
        // 更新 UI
    }
});
handler.postDelayed(runnable, 500);

// 方式三:obtainMessage(推荐,复用 Message 对象)
Message msg = handler.obtainMessage(WHAT_UPDATE);
handler.sendMessage(msg);

我的习惯:能用 post 就用 post,代码更简洁。但如果需要区分多种消息类型,用 sendMessage + what 字段更清晰。另外,obtainMessage 比 new Message() 好——它从全局消息池里复用对象,减少内存分配。

5.5 IdleHandler:空闲时的「摸鱼」机会

IdleHandler 是 MessageQueue 里的一个接口。当 Looper 处理完所有消息,进入空闲状态时,会回调 IdleHandler。

// IdleHandler 接口定义
public static interface IdleHandler {
    boolean queueIdle();
}

// 使用示例
Looper.myLooper().getQueue().addIdleHandler(new IdleHandler() {
    @Override
    public boolean queueIdle() {
        // 空闲时执行的任务
        Log.d("TAG", "主线程空闲了,做点后台工作");
        return false;  // false 表示只执行一次,true 表示每次空闲都执行
    }
});

我在项目里用过 IdleHandler 做两件事:

  • 延迟加载——页面首帧渲染完成后,利用空闲时间加载次要资源
  • 预加载数据——列表滑动停止后,利用空闲时间预加载下一页数据

避坑指南:我曾经在 IdleHandler 里做了耗时操作,导致界面卡顿。记住,IdleHandler 虽然叫「空闲」,但它仍然运行在主线程。如果任务太重,还是丢到子线程去吧。

5.6 同步屏障:让紧急消息插队

同步屏障是 MessageQueue 里一个比较「冷门」但很重要的机制。

正常情况下,消息按时间顺序排队。但有些消息需要「插队」——比如 View 的绘制请求。这时候就需要同步屏障。

// 同步屏障的工作原理
// 1. 插入一个 target 为 null 的 Message(这就是屏障)
// 2. 后续的普通消息被屏障挡住,只有异步消息能通过
// 3. 屏障被移除后,恢复正常

// 插入屏障(系统内部使用)
int token = messageQueue.postSyncBarrier();

// 移除屏障
messageQueue.removeSyncBarrier(token);

同步屏障的应用场景:

  • View 绘制——当 View 需要重绘时,系统插入屏障,让绘制消息优先执行
  • 输入事件——触摸事件需要及时响应,不能被普通消息阻塞

关键点:同步屏障只对异步消息有效。普通消息(同步消息)会被屏障挡住。Handler 默认发送的是同步消息,需要调用 setAsynchronous(true) 才能发送异步消息。

5.7 总结:一张表看清所有角色

组件 职责 关键方法 注意事项
Looper 消息循环 prepare(), loop(), quit() 子线程需手动调用 prepare()
MessageQueue 消息存储 next(), enqueueMessage() 内部是链表,next() 会阻塞
Handler 收发消息 sendMessage(), handleMessage() 注意内存泄漏
IdleHandler 空闲回调 queueIdle() 不要做耗时操作
同步屏障 消息优先级 postSyncBarrier() 系统内部使用,开发者一般不用

Handler 机制说复杂也复杂,说简单也简单。你只要记住一句话:Handler 发消息,MessageQueue 存消息,Looper 取消息并分发。剩下的细节,都是在解决「什么时候取」「取哪个」「取不到怎么办」这些问题。

嗯,如果你能把这套机制吃透,Android 的消息驱动模型你就掌握了八成。剩下的两成,藏在源码的细节里——比如 native 层的 epoll 实现、Message 的对象池复用、屏障的 token 管理。有兴趣的话,可以自己去翻翻源码,会有更多收获。


公众号:蓝海资料掘金营,微信deep3321