消息循环机制
消息循环流程
Android 消息循环主要涉及Looper、Handler、MessageQueue和Message 四个类,他们之间的关系如下图所示:

简单总结就是:Message 由 Handler 发送,按照发生时间升序保存在 MessageQueue 中,然后 Looper 不断从 MessageQueue 中取出下一条待处理的 Message ,并通知发送它的 Handler 进行处理。
各个类的作用
Message
代表一个消息(或事件),可以携带数据,持有发送它的Handler的引用。
MessageQueue
消息队列,以单链表的形式按照发生时间升序排列 Message。
提供了 enqueueMessage()用于把新消息插入到MessageQueue中,也提供了next()方法用于获取下一条待处理消息。(当队列无消息或者消息处理时间未到时,会阻塞等待。)
Looper
消息循环的入口,提供 Looper.prepare() 用于创建 Looper 线程,以及 Looper.loop()方法进入消息循环,持有 MessageQueue 的引用。
Looper.prepare() 方法用于给当前线程设置Looper,通过 ThreadLocal 将当前线程和它的 Looper 进行绑定,每个线程只能有一个Looper。
Looper.loop() 方法就是通过 MessageQueue的next()方法获取下一条待处理消息,然后通知消息的 Handler 处理消息。
public static void loop() {
//获取当前线程的 looper
final Looper me = myLooper();
//如果在调用 loop 之前没有调用 prepare,就会抛出一个异常
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取 Looper 对应的 MessageQueue
final MessageQueue queue = me.mQueue;
for (;;) {
//通过 MessageQueue 的 next 方法获取下一个消息
//在没有消息、或消息处理时间未到时可能会阻塞
Message msg = queue.next();
//消息队列为空,退出循环
if (msg == null) {
return;
}
try {
//通知 msg 对应的 Handler 处理消息
msg.target.dispatchMessage(msg);
} finally {
//...
}
//...
//回收 msg
msg.recycleUnchecked();
}
}Handler
用于发送和处理消息。提供了一系列 send 方法来将 Message 插入到 MessageQueue中,最终调用 enqueueMessage 方法。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//给 msg 的 target 赋值为当前这个 Handler
msg.target = this;
//如果 Handler 是否异步来设置 Message
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//调用 MessageQueue 的 enqueueMessage
return queue.enqueueMessage(msg, uptimeMillis);
}提供了 dispatchMessage(Message msg) 方法用于处理消息,具体逻辑如下:

对应源码:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}各个类和方法调用关系图

主线程 Looper 设置
Looper 线程的等待和唤醒(JNI 层)
MessageQueue 的 nativeInit 方法
MessageQueue 的构造方法中调用了 nativeInit 方法,这个方法在JNI层创建了一个 NativeMessageQueue ,并将它保存在 Java 层 MessageQueue 的 mPrt 变量中。
在创建 NativeMessageQueue 过程中,也在 Native 层创建了一个 Looper,Looper 内部创建了一个管道,用于控制线程的等待和唤醒。当管道中有内容可读时,就唤醒当前正在等待管道中的内容的线程。
管道就是一个文件,在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件,其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容,当管道没有内容时,这个线程就会进入等待状态,而另外一个线程通过写文件描述符来向管道中写入内容,写入内容的时候,如果另一端正有线程正在等待管道中的内容,那么这个线程就会被唤醒。这里借助了 Linux 系统的epoll 机制。
MessageQueue 的 next 方法
Message next() {
//...
//用于 native 层的变量
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//取下一条消息前需要等待的时间
int nextPollTimeoutMillis = 0;
for (;;) {
//...
//查看是否有消息
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 {
// 取出下一条msg
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 队列中已没有更多消息
nextPollTimeoutMillis = -1;
}
//....
}//end sychronized
}//end for
}主要的方法是 nativePollOnce(mPtr, nextPollTimeoutMillis),这个方法会调用NativeMessageQueue 的 pollOnce 方法,实际调用的是 Native 层的 Looper 的 pullOnce 方法,
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
......
if (result != 0) {
......
return result;
}
result = pollInner(timeoutMillis);
}
}
。
原文链接:https://blog.csdn.net/luoshengyang/article/details/6817933pollOnce 主要通过 pollInner 的返回值来判断是否需要返回。pollInner 方法通过 epoll_wait 来检查管道是否有写入事件,如果有或者等待超时,那么就会返回;否则就会在epoll_wait中进入睡眠。
如果 epoll_wait 返回值是 0,即等待超时;如果大于0,则说明有写入事件发生,这时会清空管道内容并返回。
MessageQueue 的 enqueueMessage
final boolean enqueueMessage(Message msg, long when) {
......
final boolean needWake;
synchronized (this) {
......
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
needWake = false; // still waiting on head, no need to wake up
}
}
if (needWake) {
nativeWake(mPtr);
}
return true;
————————————————
版权声明:本文为CSDN博主「罗升阳」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/luoshengyang/article/details/6817933在将消息放入消息队列后,根据情况判断是否需要唤醒。如果消息队列不为空,那么就不需要唤醒;如果消息队列为空,那么此时线程还在epoll_wait上空闲等待,就需要进行唤醒。
唤醒调用了native方法 nativeWake,这个调用NativeMessageQueue的wake方法,进而调用Native 层Looper 的wake方法。Native Looper 的wake 就是往管道中写入一个"W"字符串,这时在管道读的一端 epoll_wait 上等待的线程就会被唤醒,从而在Java 层MessageQueue 的next方法中调用的 nativePollOnce返回,此时消息队列中已经放入了新的消息,程序就会拿到消息队列的消息进行处理或等待。
总结
Looper 线程的等待和唤醒是在 native 层通过管道实现的。
在创建 MessageQueue 的时候在 Native 层也创建了对应的 NativeMessageQueue 和 Looper。NativeMessageQueue 主要用于Java 层的 MessageQueue 调用,保存在它的 mPtr 变量中。而 Native 的 Looper 则是基于管道和 epoll 机制来实现等待和唤醒的。
当通过 MessageQueue 的 next 方法获取下一条消息时,会检查是否需要等待,如果需要,就调用 epoll_wait 等待管道的写入事件,直到超时或者有写入事件时会被唤醒。
当通过 MessageQueue 的 enqueueMessage 插入消息时,如果此时线程处于空闲等待中,则会进行唤醒。唤醒通过 Native 的 Looper 的 wake 方法,向管道中写入一个“W”字符,此时正在等待的线程就回被唤醒,nativeOncePoll 方法返回,程序开始检查消息队列中的消息决定是否处理。
IdleHanlder
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}调用MessageQueue#next() 方法且第一次循环消息队列为空或者有消息但是时间没到时执行。
相关问题
为什么系统不建议在子线程访问UI?
View 并不是线程安全的,多线程访问可能使界面状态不可预测
允许多线程需要额外的同步工作,开发成本高且性能不好(借助锁)
一个Thread可以有几个Looper?几个Handler?
一个Thread 只能有一个 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));
}一个 LooperThread 可以有多个Handler。
可以在子线程直接new一个Handler吗?那该怎么做?
不可以。创建Handler时会通过当前线程的Looper来初始化内部变量和MessageQueue,如果线程还没有对应的Looper则会抛出异常。
先调用Looper.prepare()方法,然后再创建Handler,最后通过Looper.loop()进入消息循环。
Message可以如何创建?哪种效果更好,为什么?
通过 Message 的 obtain 方法创建更改,Message 类内部有静态缓存池,会缓存之前已经分发并处理过的消息对象。这样可以实现对象复用,避免多次创建对象带来的内存开销和垃圾回收。
这里的 ThreadLocal 有什么作用?
将当前线程及其对应的 Looper 进行映射,方便直接通过 Looper.myLooper()获取当前线程对应的 Looper 对象。
主线程中 Looper 的轮询死循环为何没有阻塞主线程?
如果消息队列中没有消息或者当前消息的处理时间未到,此时线程会在native 的epoll_wait函数中空闲等待,不会占用CPU资源。
当其他线程发送消息或者等待超时后,主线程被唤醒,开始检查消息队列中的消息是否需要处理。
链接
最后更新于
这有帮助吗?