4. Handler与Looper消息机制:Android消息循环模型,Handler、Looper、MessageQueue源码级分析
Android的消息机制,说白了就是一套「生产者-消费者」模型。我刚开始接触Android时,总觉得Handler就是个用来更新UI的工具。后来做底层系统开发,才发现这玩意儿是整个Android应用的生命线。
你想想看,一个App的主线程要处理触摸事件、绘制界面、执行四大组件的生命周期回调……如果所有任务同时涌进来,线程根本扛不住。所以Android设计了一套消息循环机制——Looper负责循环取消息,Handler负责发送和处理消息,MessageQueue负责排队。这三兄弟配合得天衣无缝。
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。
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立即返回,线程被唤醒。
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()。
处理消息时,优先级是这样的:
- 如果消息本身有callback(Runnable),直接执行
- 如果Handler设置了mCallback,调用mCallback.handleMessage()
- 如果以上都没有,调用Handler子类的handleMessage()
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
4.4 消息机制的整体流程
现在我们把整个流程串起来:
- 主线程启动时,ActivityThread会调用Looper.prepareMainLooper()和Looper.loop()
- 你在主线程new Handler,它自动绑定到主线程的Looper和MessageQueue
- 调用handler.sendMessage(),消息进入MessageQueue,按时间排序
- Looper.loop()不断调用queue.next()取出消息
- 如果队列为空或消息未到时间,nativePollOnce()阻塞线程
- 新消息入队时,nativeWake()唤醒线程
- 取出消息后,调用msg.target.dispatchMessage()处理
- 处理完继续循环,等待下一条消息
这个模型保证了所有任务在主线程上串行执行,避免了多线程并发访问UI的问题。
4.5 消息机制的性能优化
在实际项目中,我总结了几条优化建议:
| 场景 | 问题 | 优化方案 |
|---|---|---|
| 频繁发送消息 | 创建大量Message对象 | 使用Message.obtain()复用对象 |
| 延迟消息过多 | 队列过长,查找效率低 | 合并消息,减少延迟消息数量 |
| Handler内存泄漏 | 内部类持有外部引用 | 使用静态内部类+弱引用 |
| 主线程任务过重 | 消息处理耗时,界面卡顿 | 使用IdleHandler处理非紧急任务 |
我曾经优化过一个列表滑动卡顿的问题。原因是每个item都post了一个延迟100ms的Runnable,导致MessageQueue里积压了几百个消息。后来改成统一用一个Handler处理,卡顿问题就解决了。
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()立即返回。
嗯,Handler机制就讲到这里。它看似简单,但底层涉及线程同步、阻塞唤醒、事件驱动等知识。理解透彻了,对Android系统级的开发会很有帮助。