Android 消息机制深入源码分析 [ 一 ]
Android 消息机制之 ThreadLocal 深入源码分析 [ 二 ]
Android 消息机制之 Looper 深入源码分析 [ 三 ]
Android 消息机制之 Message 与消息对象池的深入源码分析 [ 四 ]
Android 消息机制之 MessageQueue 深入源码分析 [ 五 ]
Android 消息机制之初识Handler [ 六 ]
Android 消息机制之 Handler 发送消息的深入源码分析 [ 七 ]
Android 消息机制之 MessageQueue.next() 消息取出的深入源码分析 [ 八 ]
Android 消息机制之消息的其他处理深入源码分析 [ 九 ]
Android 消息机制总结 [ 十 ]
紧接上一章消息的发送, 本章内容为消息的取出分析学习.
消息的取出主要是通过 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
的循环体中.
好了, 本章分析学习就到这里结束了, 下一章将会分析学习 消息分发处理, 消息的移除, 以及消息的其他操作.