4. Handler与Looper消息机制:Android消息循环模型,Handler、Looper、MessageQueue源码级分析

Android的消息机制,说白了就是一套「生产者-消费者」模型。我刚开始接触Android时,总觉得Handler就是个用来更新UI的工具。后来做底层系统开发,才发现这玩意儿是整个Android应用的生命线。

你想想看,一个App的主线程要处理触摸事件、绘制界面、执行四大组件的生命周期回调……如果所有任务同时涌进来,线程根本扛不住。所以Android设计了一套消息循环机制——Looper负责循环取消息,Handler负责发送和处理消息,MessageQueue负责排队。这三兄弟配合得天衣无缝。

核心要点: Handler机制的本质是「线程间通信」,但它的底层实现依赖于「线程局部存储(ThreadLocal)」和「管道(pipe)」或「eventfd」的唤醒机制。

4.1 消息循环模型:Looper的诞生与运行

每个线程只能有一个Looper。为什么?因为Looper内部维护了一个ThreadLocal对象,它保证了每个线程只能绑定一个Looper实例。我曾在项目中遇到过一个问题:子线程里直接new Handler,结果崩溃了。原因就是子线程默认没有Looper。

看看Looper的创建过程:

// Looper.prepare() 的核心逻辑
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));
}

嗯,这里要注意:prepare()方法只能调用一次,否则会抛异常。Looper的构造方法里会创建MessageQueue:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

然后调用loop()方法进入死循环:

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called");
    }
    final MessageQueue queue = me.mQueue;
    
    for (;;) {
        Message msg = queue.next(); // 可能会阻塞
        if (msg == null) {
            // 消息队列已退出
            return;
        }
        msg.target.dispatchMessage(msg);
        msg.recycleUnchecked();
    }
}

这个死循环会不会导致应用卡死?很多初学者都有这个疑问。其实不会——当没有消息时,queue.next()会阻塞在native层,不消耗CPU。我做过实验,主线程Looper空闲时,CPU占用率几乎为0。

避坑指南: 我曾经在子线程里忘记调用Looper.loop(),结果Handler发送的消息永远得不到处理。记住:prepare()和loop()必须成对出现。

4.2 MessageQueue:消息的排队与唤醒

MessageQueue内部维护了一个消息链表,按时间排序。插入消息时,会根据when字段(消息触发时间)找到合适的位置插入。

看看enqueueMessage()的核心逻辑:

boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // 插到头部
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 插入到中间或尾部
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
            }
            msg.next = p;
            prev.next = msg;
            needWake = false;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

这里有个关键点:当消息插入到头部,且当前线程正在阻塞等待消息时,需要唤醒线程。nativeWake()底层通过eventfd写入一个字节,让epoll从等待中返回。

再看看next()方法如何阻塞:

Message next() {
    int pendingIdleHandlerCount = -1;
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        nativePollOnce(mPtr, nextPollTimeoutMillis);
        
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // 处理屏障消息
                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;
                }
            } else {
                // 没有消息,无限等待
                nextPollTimeoutMillis = -1;
            }
            
            // 处理空闲任务
            if (pendingIdleHandlerCount < 0) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount == 0) {
                mBlocked = true;
                continue;
            }
            // ... 执行IdleHandler
        }
    }
}

nativePollOnce()是阻塞的关键。它底层使用epoll机制,等待eventfd或管道的事件。当有消息入队时,nativeWake()会写入数据,epoll立即返回,线程被唤醒。

注意: 屏障消息(Sync Barrier)是一种特殊机制。当插入一个target为null的消息时,后续的同步消息都会被阻塞,直到屏障被移除。这常用于VSync信号处理,保证绘制任务优先执行。

4.3 Handler:消息的发送与处理

Handler的作用很简单:发送消息和处理消息。但它的设计很巧妙——Handler绑定到创建它的线程的Looper上。

看看Handler的构造:

public Handler(Callback callback, boolean async) {
    // ... 检查内存泄漏
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
            + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

发送消息的几种方式:

// 方式1:直接发送
handler.sendMessage(msg);
// 方式2:延迟发送
handler.sendMessageDelayed(msg, 1000);
// 方式3:发送Runnable
handler.post(new Runnable() { ... });
// 方式4:发送空消息
handler.sendEmptyMessage(MSG_ID);

这些方法最终都会调用sendMessageAtTime(),然后走到MessageQueue.enqueueMessage()。

处理消息时,优先级是这样的:

  1. 如果消息本身有callback(Runnable),直接执行
  2. 如果Handler设置了mCallback,调用mCallback.handleMessage()
  3. 如果以上都没有,调用Handler子类的handleMessage()
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
个人经验: 我建议优先使用post(Runnable)方式,代码更简洁。但要注意,Runnable内部如果持有外部引用,容易造成内存泄漏。我曾经在Activity里post了一个Runnable,Activity销毁后Runnable还在队列里,导致Activity无法被GC回收。

4.4 消息机制的整体流程

现在我们把整个流程串起来:

  1. 主线程启动时,ActivityThread会调用Looper.prepareMainLooper()和Looper.loop()
  2. 你在主线程new Handler,它自动绑定到主线程的Looper和MessageQueue
  3. 调用handler.sendMessage(),消息进入MessageQueue,按时间排序
  4. Looper.loop()不断调用queue.next()取出消息
  5. 如果队列为空或消息未到时间,nativePollOnce()阻塞线程
  6. 新消息入队时,nativeWake()唤醒线程
  7. 取出消息后,调用msg.target.dispatchMessage()处理
  8. 处理完继续循环,等待下一条消息

这个模型保证了所有任务在主线程上串行执行,避免了多线程并发访问UI的问题。

关键点: Handler机制的核心不是「跨线程」,而是「线程内串行化」。子线程通过Handler发送消息,消息最终在Handler绑定的线程(通常是主线程)上执行。

4.5 消息机制的性能优化

在实际项目中,我总结了几条优化建议:

场景 问题 优化方案
频繁发送消息 创建大量Message对象 使用Message.obtain()复用对象
延迟消息过多 队列过长,查找效率低 合并消息,减少延迟消息数量
Handler内存泄漏 内部类持有外部引用 使用静态内部类+弱引用
主线程任务过重 消息处理耗时,界面卡顿 使用IdleHandler处理非紧急任务

我曾经优化过一个列表滑动卡顿的问题。原因是每个item都post了一个延迟100ms的Runnable,导致MessageQueue里积压了几百个消息。后来改成统一用一个Handler处理,卡顿问题就解决了。

注意: 不要在主线程的MessageQueue里post耗时操作。即使使用IdleHandler,也要保证任务执行时间短。否则会影响触摸事件和绘制的响应。

4.6 消息机制的底层实现

MessageQueue的native层实现涉及三个关键文件:

  • android_os_MessageQueue.cpp:JNI层,桥接Java和C++
  • Looper.cpp:核心实现,包含epoll和管道/eventfd
  • Looper.h:头文件,定义Looper类

native层的Looper使用epoll监听多个文件描述符:

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        // 处理响应
        while (mResponseIndex < mResponses.size()) {
            // ...
        }
        
        // 没有待处理响应,开始等待
        if (timeoutMillis != 0) {
            result = pollInner(timeoutMillis);
        }
    }
}

int Looper::pollInner(int timeoutMillis) {
    // 调整超时时间
    // ...
    
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    
    // 处理事件
    for (int i = 0; i < eventCount; i++) {
        if (eventItems[i].events & EPOLLIN) {
            // 可读事件,通常是新消息到达
            awoken();
        }
    }
    // ...
}

epoll_wait()是真正的阻塞点。当没有事件时,线程挂起,不消耗CPU。当有消息入队时,nativeWake()通过eventfd写入数据,epoll_wait()立即返回。

避坑指南: 我曾经遇到过一个问题:子线程Looper退出后,MessageQueue里还有未处理的消息。这些消息永远不会被处理,如果消息持有资源引用,就会造成内存泄漏。所以退出Looper前,最好清空消息队列。

嗯,Handler机制就讲到这里。它看似简单,但底层涉及线程同步、阻塞唤醒、事件驱动等知识。理解透彻了,对Android系统级的开发会很有帮助。

Android Handler消息机制流程图 子线程 Handler.sendMessage() MessageQueue 消息链表(按时间排序) Looper.loop() nativePollOnce() epoll_wait() 阻塞等待 nativeWake() dispatchMessage() 主线程 发送端 存储队列 循环调度 阻塞等待 消息处理