上一章学习了消息机制中的 Message 与 Message 的消息对象池. 本章继续学习消息机制中 MessageQueue 消息队列的相关概念.
尽管 MessageQueue
叫消息队列, 但是它的内部实现却并不是用的队列, 实际上它是通过一个单链表的数据结果来维护消息列表. (单链表在插入与删除上很有优势)
MessageQueue
主要包括两个操作, 插入和读取. 但是读取操作本身会伴随着删除操作. 插入和读取对应的方法分别是 enqueueMessage
与 next
.
-
enqueueMessage
作用是往消息队列中插入一条数据. -
next
作用是从消息队列中取出一条消息并将其从消息队列中移除.next
是一个无限循环的方法, 如果消息队列中没有消息, 那么next
方法会一直阻塞, 当有新消息来的时候,next
会返回这条消息并将其从链表中移除.
消息队列中所有的 Message
都是按照时间从前往后有序排列的.
1. 成员变量
private final boolean mQuitAllowed
用于表示消息队列是否可以被关闭, 主线程的消息队列不可关闭.
private long mPtr; // used by native code
用于保存 native 代码中 MessageQueue 的指针.
Message mMessages
在 MessageQueue 中所有的 Message 都是以链表的形式组织在一起的, mMessage 保存了消息链表的头部. 也可以说它就是链表的本身.
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
当 Handler 线程处于空闲状态的时候(MessageQueue 没有其他 Message), 可以利用它来处理一些事物, 该变量就是用于保存这些空闲时候需要处理的事物.
private IdleHandler[] mPendingIdleHandlers;
保存将要被执行的 Idlehandler
private boolean mQuitting;
标识 MessageQueue 是否正在关闭.
private boolean mBlocked;
标识 MessageQueue 是否阻塞
private int mNextBarrierToken;
在 MessageQueue 中有一个概念叫做障栅, 它用于拦截同步的 Message, 阻止这些消息被执行. 只有异步的 Message 才会放行. 障栅本身也是一个 Message, 只是它的 target 为 null, 并且 arg1 属性用于区分不同的障栅, 所以该变量就是用于不断累加生成不同的障栅.
2. IdleHandler 接口
在 MessageQueue.java 811 行 中, 有一个内部接口为 IdleHandler.
public static interface IdleHandler {
boolean queueIdle();
}
这是一个回调接口, 当线程空闲时可以利用它来处理一些业务.
boolean queueIdle()
当消息队列内所有的
Message
都执行完之后, 这个方法会被调用.
返回true, IdleHandler
会一直保持在消息队列中.
返回 false, 会执行完改方法后移除IdleHandler.
需要注意的是, 当消息队列中还有其他的 Delay Message (延迟消息)
, 并且这些 Message
还没到执行时间的时候, 由于线程空闲, 所以这个方法也可能会被调用, IdleHandler
也可能会被执行.
IdleHandler
就是一个简单的回调接口, 内部就是一个带有返回值的方法, 在使用的时候只需要实现该接口并加入到 MessageQueue
中就可以了.
//例:
MessageQueue messageQueue = Looper.mQueue();
messageQueue.addIdleHandler(new MessageQueue.IdleHandler(){
@Override
public boolean queueIdle(){
return false;
}
});
接着看一下 addIdleHandler
方法做了些什么.
MessageQueue.java 117行
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
- 官方翻译:
添加一个新的
IdleHandler
到消息队列中, 当IdleHandler
的回调方法为false
的时候, 该IdleHandler
在被执行后会立即移除, 也可以通过调用removeIdleHandler(IdleHandler handler)
方法移除指定的IdleHandler.
任何线程调用该方法都是安全的.
方法内部很简单, 就是三个步骤
- 做非空判断.
- 加同步锁.
- 将要添加的
IdleHandler
添加至mIdleHandlers (ArrayList)
2. MessageQueue 中的 Message 的分类
在 MessageQueue 中, Message 被分为 3 类.
- 同步消息
正常情况下我们通过
Handler
发送的Message
都属于同步消息, 除非我们在发送的时候指定该消息是一个异步消息. 同步消息会按顺序排列在队列中. 除非指定Message
的执行时间. 否则Message
会按顺序执行.
- 异步消息
想要往消息队列中发送异步消息, 我们必须在初始化
Handler
的时候通过构造函数public Handler(boolean async)
指定Handler
是异步的. 这样的Handler
在将Message
加入消息队列的时候, 就会将Message
设置为异步的.
- 障栅(Barrier)
障栅
(Barrier)
, 是一种特殊的Message
, 它的target
为null
, 并且arge1
属性被用作障栅的标识符来区分不同的障栅.
障栅的作用是用于拦截队列中的同步消息, 放行异步消息. 就好像交警一样, 在道路拥挤的时候决定哪些车可以先通过, 在这里这些先通过的车辆指的就是异步消息.
(只有 障栅的target
才会为null
, 如果我们自己试图设置Message
的target
为null
的话会报异常)
3. 添加障栅 Barrier
首先看一下 障栅的添加. 在 MessageQueu.java
中 障栅的添加方式有两种.
1. postSyncBarrier(). 代码位于 MessageQueu.java 461 行.
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
- 官方翻译:
- 向
Looper
的消息队列中发送一个同步的Barrier
- 如果没有发送同步的
Barrier
, 消息处理像往常一样, 该怎么处理就怎么处理. 当发现Barrier
后, 队列中后续的同步消息会被阻塞, 直到通过调用removeSysncBarrier()
释放指定的Barrier
.- 这个方法会导致立即退出所有后续发布的同步消息, 直到满足释放指定的
Barrier
, 而异步消息不受Barrier
的影响. 并按照之前的流程继续处理.- 必须使用相同的
token
去调用removeSysncBarrier()
, 来保证插入的Barrier
和移除的是同一个, 这样可以确保消息队列可以正常运行, 否则应用程序可能会挂起.- 返回值是
Barrier
的唯一标识符, 持有这个token
去调用removeSysncBarrier()
方法才能达到真正的释放Barrier
可以看到方法内部又调用了另外一个同名有参的方法, 并传入了手机开机到现在的时间.
2. postSyncBarrier(long when). 代码位于 MessageQueu.java 465 行.
private int postSyncBarrier(long when) {
synchronized (this) {
//分析 1
final int token = mNextBarrierToken++;
//分析 2
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
//分析 3
Message prev = null;
Message p = mMessages;
if (when != 0) {
//分析 4
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
//分析 5
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
//分析 6
return token;
}
}
- 分析
- 获取障栅的唯一标识, 然后自增该变量作为下一个障栅的标识, 通过
mNextBarrierToken++
可以得知, 这些唯一标识是从 0 开始自增的.- 从
Message
的消息对象池中获取一个Message
对象赋值给msg
作为障栅. (上一章学习过了消息对象池, 忘记的同学可以去翻看一下).
并且赋值when
与arg1
的值.(arg1 为障栅的标识符)
, 同时调用msg.makeInUse()
标记msg
正在被使用,- 创建变量
prev
和p
, 并把 消息队列的第一个元素mMessage
赋值给p
, 为第四步做准备. 此时p
就为消息队列的第一个元素.- 通过消息队列中的第一个
Message
对象p
的when
属性与要添加障栅的when
属性比较, 如果比要添加障栅的时间小, 就把第一个消息向后移动一位, 依次类推, 从而决定障栅在消息队列中的位置. 比如是放在消息队列的头部, 还是第二个位置. 如果在头部, 则拦截后面所有的同步消息, 如果在第二个位置, 则会放过第一个, 然后拦截后面的同步消息.- 将障栅对象
msg
插入到消息队列中.
prev != null
代表障栅的位置不会插入在消息队列的头部, 那就需要考虑到障栅的后面位置和前面位置Message
的衔接.prev == null
表示障栅要插入的位置在消息队列的头部, 这样的话, 只需要与后面位置的Message
进行衔接即可. 然后再赋值给mMessage
.- 返回
token
- 解析
简单来说每次添加障栅到消息队列的时候, 都会先通过
when
进行比较, 根据不同的情况会将障栅插入到不同的位置.
- 当
Message.when < Barrier.when
的时候, 可以理解为第一个Message
的执行时间早于障栅.
消息队列中第一个消息的执行时间早于障栅的情况
- 当
Message.when > Barrier.when
的时候, 也就是第一个Message
的执行时间晚于障栅.
消息队列中第一个消息的执行时间晚于障栅的情况
注意: 在上面的添加障栅的代码中, 可以看到 msg
这个障栅的 target
都是 null
, 自始至终都没有被赋值过, 上面也说过自有障栅的 target
才会为 null
. 这也是后面在移除障栅的时候通过这个来判断的条件之一.
4. 移除障栅
移除障栅的方法为 removeSyncBarrier()
, 代码位于 MessageQueue.java 504 行
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
//还是获取消息队列中的第一个元素
Message p = mMessages;
//遍历消息队列所有元素, 找到与 token 对应的障栅
//如果有障栅, 那么 prev 要么为 null, 表示消息队列的第一位就是障栅
//如果 prev 不为 null, 则 prev.next 为障栅, 而 p 的第一个元素为障栅
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
//是否需要唤醒
final boolean needWake;
//如果障栅不是消息队列中的第一个元素.
if (prev != null) {
//可以理解为: 将消息队列中障栅的下一个消息对象,
// 赋值给消息队列的障栅位置.达到移除的目的.
prev.next = p.next;
//因为有元素,所以不需要唤醒
needWake = false;
} else {
//如果障栅就是第一个元素,则直接把消息队列中的第一个元素指向障栅的下一个元素
mMessages = p.next;
//赋值完以后,判断消息队列中是否有消息,是否需要唤醒
needWake = mMessages == null || mMessages.target != null;
}
//将 P 回收放入消息对象池.
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
- 解析
移除障栅的方法也很简单, 就是不断的遍历整个消息队列, 找到与
token
匹配的障栅后移除, 移除就是跳过障栅, 将障栅的上一个消息与障栅的下一个消息衔接起来.
-
MessageQueue.removeMessages(Handler h, int what, Object object)
: 从消息队列中删除所有符合指定条件的 Message -
MessageQueue.removeAllFutureMessagesLocked()
: 删除所有未来的消息. -
MessageQueue.quit(boolean safe)
: 关闭消息队列(在 Looper 篇中已经分析过) -
MessageQueue.next()
从消息队列中取出消息交给 Looper. (核心) -
MessageQueue.hasMessages(Handler h, int what, Object object)
: 查看消息是否存在
这些方法都会放到 Handler
中有关消息的发送, 取出以及其他操作的文章中. 本篇文章主要是了解一下在 MessageQueue
中对 Message
的几种分类, 以及如何添加障栅(Barrier), 移除障栅的操作. 下一章正式开始学习 消息机制中的 Handler
.
网友评论