Android 消息机制之 MessageQueue.next(
2020-08-27 本文已影响0人
__Y_Q
紧接上一章消息的发送, 本章内容为消息的取出分析学习.
消息的取出主要是通过 Looper 的 loop 方法. 这个方法在第三章的时候已经分析过, 分为以下几步
- 获取
Looper对象 - 获取
MessageQueue消息队列对象 - 死循环遍历
- 通过
queue.next()来从MessageQueue的消息队列中获取一个Message msg对象 - 通过
msg.target,dispatchMessage(msg)来处理消息 - 通过
msg.recycleUnchecked()方法来回收Message到消息对象池中.
其中 Message.recycleUnchecked() 在第四章的时候已经分析过, 那么现在就剩下 MessageQueue.next() 与 handler.dispatchMessage() . 那么先来看 MessageQueue.next()
1. MessageQueue.next()
MessageQueue.java 310 行, 代码过多, 将分段分析.
Message next() {
//分析 1
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//分析 2
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//分析 3
int nextPollTimeoutMillis = 0;
- 分析:
- 如果消息循环已经退出了, 则在这里直接
return, 因为调用了disposed()方法后,mPtr = 0;
- 记录空闲时间处理的 IdleHandler 的数量. 初始为 -1
native需要用到的变量. 初始化为 0, 如果大于 0, 表示还有消息待处理(未到执行时间). -1表示阻塞等待
//分析 4
for (;;) {
//分析 5
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//分析 6
nativePollOnce(ptr, nextPollTimeoutMillis);
//分析 7
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//分析 8
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
- 分析
- 开启死循环 (循环内容一直到最后)
- 如果还有消息未处理, 就刷新
Binder命令, 一般在阻塞前调用
- 调用
native方法, 当nextPollTimeoutMillis == -1的时候就阻塞等待, 直到下一条消息可用为止. 否则就继续向下执行. 还记得第七章发送消息时候消息入队操作的最后吗? 里面有一个nativeWake()唤醒. 就是唤醒此处. 没有消息的时候, 这里就处于阻塞状态. 当我们发送消息的时候, 这里就会被唤醒.
- 加上同步锁, 然后获取从开机到现在的时间, 获取消息链表头部元素,
- 判断第一个消息是不是障栅. (在前面第五篇中说过: 只有障栅的
tatget才为null), 如果第一个消息是障栅, 则又开启一个循环, 取出第一个异步消息, 从do..while这段代码中. 可以印证出障栅会拦截所有的同步消息.
如果msg != null && ! msg.isAsynchronous()这个条件成立, 说明就是同步消息, 那么就跳出同步消息继续循环, 直到找到第一条异步消息并赋值给
.就退出do..while循环.
//分析 9
if (msg != null) {
//分析 10
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else { //分析 11
mBlocked = false;
//分析 12
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
//分析 13
nextPollTimeoutMillis = -1;
}
- 分析
- 判断消息是否为
null
如果进入了分析 8 中的if逻辑, 那么到这一步,msg要么是一个异步消息, 要么为null. 不可能是同步消息
如果没有进入分析 8, 说明头部消息不是障栅, 需要判断是否是可执行的异步消息或者同步消息.
- 如果当前时间小于消息的执行时间, 表示当前可执行的消息还未到执行时间, 则记录下剩余时间.
- 如果当前时间大于等于消息的执行时间, 表示当前消息的执行时间已经到了, 接着将
MessageQueue.mBlocked设置为false表示MessageQueue不阻塞,mBlocked变量与消息入队时,需要不需要唤醒
- 这个
if..esle..判断内的逻辑就是将需要立刻执行的消息从消息队列中抽出来, 然后再将消息队列组合起来. 再将要执行消息的next赋值为null,并标记为正在使用. 最后把要执行的消息返回出去. 获取消息结束.
例如: 消息链表中有三个消息 A -> B -> C, A是障栅, B是异步, C是同步. 分析 12 走完, 就变成了, B 是单独的一个消息, 并将 B.next 置为 null, 最后组合后的消息链表就为 A -> C.
- 如果分析9 的判断不成立, 则表示目前没有可执行的消息, 设置
nextPollTimeoutMillis = -1. 在分析 3 中说过这个变量的作用表示是否阻塞.
//分析 14
if (mQuitting) {
dispose();
return null;
}
//分析 15
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//分析 16
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
//分析 17
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
注意: 当代码开始执行这里的时候, msg 要么为 null, 要么未到执行时间.
- 分析
- 官方翻译为:现在已处理所有挂起的消息,请处理退出消息。需要关闭消息队列, 返回
null, 通知Looper停止循环.
- 当第一次循环才会在空闲的时候去执行
IdleHanler, 从代码可以看出所谓的空闲状态指的就是, 目前没有任何可执行的Message, 这里的可执行有两个要求, 当前Message不会被障栅拦截, 当前Message到达了执行时间. 才会为变量pendingIdleHandlerCount赋值.
- 如果没有在空闲时需要执行的
IdleHandler. 这里是消息队列阻塞(死循环)的重点, 在msg = null或者未到执行时间的情况下, 表示消息队列空闲, 但是也没有可执行的idleHandler, 那么就把mBlock变量置为true, 表示需要唤醒, 并开始下一次循环. 就会回到上面的分析 5 和分析 6, 这个时候nextPollTimeoutMillis要么为 -1, 要么就为上个消息剩下要执行的时间. 那么分析 5 肯定成立, 接着刷新binder命令, 然后在分析 6 中就开始阻塞, 只要不是 0, 就会阻塞. 等要执行的时间到了就会被唤醒. 或者当有新的消息入队的时候. 就会根据mBlock的值来判断是否要唤醒消息队列.
- 如果有需要在空闲时执行的
IdleHandler, 接着判断是否初始化过mPendingIdleHandlers数组, 最小4 个长度. 并把要执行的IdleHandler赋值给mPendingIdleHandlers数组.
//分析18
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
//分析 19
pendingIdleHandlerCount = 0;
//分析 20
nextPollTimeoutMillis = 0;
}
}
注意: 执行到这里, 说明目前没有可执行的 Message, 但是有可以在空闲时执行的 IdleHandler.
官方对这个循环的注释为:
- 执行
idleHandler. 我们只会在第一次迭代时到达此代码块。为什么呢, 这个稍后分析, 先分析完 19 和 20.
IdHandler只会在消息队列阻塞之前执行一次, 执行之后,pendingIdleHandlerCount赋值为 0, 之后就不会再执行. 一直到下一次调用MessageQueue.next()方法.
- 当执行了
IdleHander之后, 会消耗一段时间, 这时候消息队列里可能已经有消息到达可执行时间, 所以重置nextPollTimeoutMillis回去重新检查消息队列.
关于分析 18 的疑问. 为什么只会在第一次循环的时候会执行这里呢.
也就是说只有在
MessageQueue.next方法的死循环, 第一次循环的时候,msg为null或者msg未到执行时间, 并且有可执行的空闲IdleHandler的情况下会 执行. 或者下次调用MessageQueue.next()方法. 为什么呢? 一起来分析一下,
第一次循环开始,
- 分析 6 那里肯定不会阻塞, 因为
nextPollTimeoutMillis初始值为 0.- 然后到分析 9 , 如果
msg != null并且有需要立即执行的消息的话, 就直接跳出死循环了, 我们假设msg为null或者msg未到执行时间. 那么在分析 9 内, 会对nextPollTimeoutMillis赋值,- 接着到分析 15,
pendingIdleHandlerCount初始值为 -1, 判断成立, 给pendingIdleHandlerCount赋值- 到分析 16, 条件不成立, (因为我们假设有可执行的空闲
IdleHandler). 如果没有则直接就进行下次一次循环了, 下一次循环到分析 6 处, 就会阻塞了.- 分析 18, 开始执行
IdleHandler. 执行一个, 就从mIdleHandlers中移除一个.- 分析 19,
pendingIdleHandlerCount和nextPollTimeoutMillis都赋值为 0,第二次循环开始
- 分析 6 不阻塞, 因为在第一次循环的分析 20 处被重置了, 需要重新检查消息队列.
- 分析 9 如果有消息到执行时间了, 会直接
return, 没有消息或者还是未到时间就再对nextPollTimeoutMillis赋值 -1 或者剩余执行时间. 接着向下走.- 分析 15. 这里的判断就不会成立了, 因为在第一次循环最后分析 19 处
pendingIdleHandlerCount被置为 0 了. 所以跳到分析 16.- 分析 16.
pendingIdleHandlerCount <= 0条件成立. 跳出本次循环, 开始进入第三次循环了. 然后在第三次循环中的分析 6, 就开始阻塞. 直到被唤醒.
总结
总的来说当在 Looper.loop() 方法的死循环内, 调用MessageQueue.next() 方法获取一个 Message 的时候, 大致会分为以下几步.
MessageQueue会先判断队列中是否有障栅存在
- 有: 返回第一个异步消息,
- 没有: 逐个返回同步消息
- 当
MessageQueue中没有任何消息可以处理或者未到消息的执行时间的时候, 就会进入阻塞状态等待新的消息到来被唤醒. 或者有消息的执行时间到了被唤醒. 在阻塞之前会执行一次IdleHandler.- 当
MessageQueue被关闭的时候, 成员变量mQutting会被标记为true, 然后在Looper试图从MessageQueue取消息的时候返回null. 而Message = null就是告诉Looper消息队列已经关闭, 应该停止死循环了.(在第三篇Looper.loop()方法分析中有说明 )Handler中实际上有两个无限循环体, 一个是在Looper.loop()中的循环体, 以及MessageQueue中的循环体. 真正的阻塞是在MessageQueue的循环体中.
好了, 本章分析学习就到这里结束了, 下一章将会分析学习 消息分发处理, 消息的移除, 以及消息的其他操作.