❝Android 消息机制中的 MessageQueue 可以存放三种类型的消息,普通消息、消息屏障和异步消息。其中消息屏障和异步消息搭配使用,可以达到屏蔽普通消息、优先处理异步消息的目的。❞
「目录:」
1.如何插入一个消息屏障?
2.如何删除一个消息屏障?
3.如何插入一个异步消息?
4.消息屏障对插入消息有什么影响?
5.消息屏障是如何优先处理异步消息的?
6.Framework 中哪里使用了消息屏障?
1. 如何插入一个消息屏障?
见 MessageQueue 的 postSyncBarrier 方法:
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
//按时间排序插入到队列中...
return token;
}
}
1.消息屏障不需分发处理,没有 target Handler,后续也会根据有无 target 来判断是否为消息屏障
2.消息屏障也是有时间戳的,并且只会对后面的消息起到屏障作用,不会影响前面的消息
3.消息屏障插入后无需唤醒线程,因为消息屏障原本的目的就是打算屏蔽消息处理的
4.插入后会返回一个 token,用于后续移除指定 token 的消息屏障
5.方法为 private,外部调用需反射
2. 如何删除一个消息屏障?
见 MessageQueue 的 removeSyncBarrier 方法:
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
//从队列中删除这个消息屏障...
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
1.根据无 target 及 token 匹配找到对应的消息屏障
2.删除屏障后可能需要唤醒线程,是否唤醒取决于当前是否是因为消息屏障而阻塞的
3. 如何插入一个异步消息?
Message 的 setAsynchronous 为开放 API,直接调用设置即可,比如在 ViewRootImpl 中对输入事件的处理:
public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = event;
args.arg2 = receiver;
Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
msg.setAsynchronous(true); //异步消息
mHandler.sendMessage(msg);
}
由于输入事件需要快速响应,优先级比较高,所以设置为异步消息,避免被消息屏障屏蔽掉
4. 消息屏障对插入消息有什么影响?
见 MessageQueue 的 enqueueMessage 方法:
//省略部分代码...
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) { //插入到队列头部
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else { //插入到队列中间
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) { //唤醒
nativeWake(mPtr);
}
1.如果插入到队列头部,那么只要当前线程是休眠的,就要唤醒,不管有没有消息屏障,因为消息屏障不会影响在它之前的消息
2.如果插入到队列中间且队列头消息为消息屏障,那还要判断插入的消息是不是最早的异步消息,如果是才唤醒线程。因为如果之前已经有异步消息,那说明已经对之前的异步消息做过唤醒或休眠指定时间的处理了,不用再此唤醒
5. 消息屏障是如何优先处理异步消息的?
见 MessageQueue 的 next 方法:
Message next() {
//省略部分代码...
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//省略部分代码...
}
1.如果当前消息不是消息屏障,那异步消息和普通消息无异,都会按照时间排序依次执行
2.如果当前消息为消息屏障,就会去找队列中的异步消息,如果没有异步消息,就无限休眠;如果有,就根据这个异步消息的处理时间去分发处理或休眠
6. Framework 中哪里使用了消息屏障?
ViewRootImpl 中界面绘制时使用了消息屏障:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//插入一个消息屏障,屏蔽普通消息的处理
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除消息屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
}
}
与输入事件一样,界面绘制也是优先级高的消息,需要优先处理,所以这里插入消息屏障 block 其他普通消息,以达到优先处理界面绘制的目的。
网友评论