我们在 Handler线程通信机制:实战、原理、性能优化! 中已经知道了Handler的消息同步机制,在MessageQueue的next方法中,有一段逻辑是处理同步屏障的,我们在本章中将分析同步屏障是什么?原理?以及它在Android中的使用。
同步屏障机制的原理
在Handler的介绍中,我们已经了解了同步屏障,但当时这块不是重点介绍的内容,我们来复习一下它的实现原理。
MessageQueue的next方法:
Message next() {
……
for (;;) {
……
synchronized (this) {
// Try to retrieve the next message. Return if found.
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());
}
……
}
在MessageQueue的next方法中,获取当前执行的消息时,首先对消息进行了判断,如果当前消息是同步屏障消息,则屏蔽后续所有同步消息的执行,异步消息不受影响。
同步屏障消息对象的target属性是null,也就是没有持有Handler对象的引用。
这里我们回想一下,如果消息通过Handler的sendMessage方法添加到消息队列时,会判断target是否为null,如果是null则会抛出错误,所以同步屏障消息的添加不是通过Handler的常用方法添加的。
同步屏障的添加和移除
既然同步屏障不是通过Handler的sendMessage等方法添加的,那么它又是通过哪个方法进行设置的呢?
我们在MessageQueue中发现是postSyncBarrier方法添加的同步屏障消息。
同步屏障的添加
MessageQueue的postSyncBarrier方法:
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
//获取token值
final int token = mNextBarrierToken++;
//生成一个同步屏障消息
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
//将同步屏障消息插入到合适的消息队列位置
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
逻辑解析:
- postSyncBarrier方法调用了有参的postSyncBarrier方法,传递了一个执行时间的参数。
- 在同步代码块中,执行添加逻辑。
- 首先通过mNextBarrierToken属性获取一个int类型的token,它也是该方法的返回值,用于移除屏障时使用。
- 获取了一个新的Message对象,注意这里没有设置target属性,正常的消息target属性不能为空。
- 根据when属性,将同步屏障消息添加到消息队列的合适位置。
- 最后返回token。
注意:添加同步屏障,不需要唤醒线程。
同步屏障的移除
同步屏障添加后,并不能自动移除,需要调用系统接口手动移除。
MessageQueue的removeSyncBarrier方法:
public void removeSyncBarrier(int token) {//参数token就是我们添加同步屏障时的返回值。
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
//如果当前消息不是同步屏障消息,则遍历消息链表进行查找
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {//没有找到则抛出error
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.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);
}
}
}
逻辑解析:
- MessageQueue的removeSyncBarrier方法可以移除同步屏障,参数是添加消息屏障的返回值token,用于移除指定的消息屏障。
- removeSyncBarrier方法首先判断当前消息mMessages,是否是同步屏障消息,如果当前消息不是同步屏障消息,则遍历消息链表进行查找。
- 如果没有找到同步屏障消息,则抛出Error,否则继续执行。
- 如果同步屏障消息尚未执行,则删除该消息即可,不用执行唤醒消息循环。
- 否则,当前消息既是同步屏障消息,删除该消息后,需要根据条件,进一步判断是否需要唤醒消息循环。
- 最后,如果需要唤醒消息循环,并且当前消息循环未执行退出操作,则调用nativeWake方法执行唤醒操作。
同步消息屏障在Android系统中的使用场景
在Android系统中,当应用进行视图的刷新渲染时,用到了同步消息屏障,这里使用的目的是:优先保障视图的刷新逻辑的执行,避免UI线程执行其他同步任务导致的卡顿等问题。
ViewRootImpl的scheduleTraversals方法:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
ViewRootImpl的scheduleTraversals方法,调用了MessageQueue的postSyncBarrier方法来添加一个同步消息屏障,然后执行视图渲染相关逻辑。
ViewRootImpl的unscheduleTraversals方法
当系统完成UI视图的渲染刷新后,会调用ViewRootImpl的unscheduleTraversals方法移除同步屏障。
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
总结
本文章我们学习了Android消息循环的同步屏障机制原理,以及Android系统中的使用,我们来简单总结下:
- 在MessageQueue的next方法中,获取当前执行的消息时,首先对消息进行了判断,如果当前消息是同步屏障消息,则屏蔽后续所有同步消息的执行,异步消息不受影响。
- 同步屏障消息对象的target属性是null,也就是没有持有Handler对象的引用。
- 同步屏障的消息可以通过MessageQueue的postSyncBarrier方法进行添加,添加时,不需要进行消息循环的唤醒操作。
- MessageQueue的postSyncBarrier方法的返回值是一个int类型的token,用于移除屏障时使用。
- 同步屏障添加后,并不能自动移除,需要调用系统接口手动移除。
- MessageQueue的removeSyncBarrier方法可以移除同步屏障,参数是添加消息屏障的返回值token。
- 如果同步屏障消息尚未执行,则删除该消息即可,不用执行唤醒消息循环。
- 当前消息既是同步屏障消息,删除该消息后,需要根据条件,进一步判断是否需要唤醒消息循环,如果需要唤醒消息循环,并且当前消息循环未执行退出操作,则调用nativeWake方法执行唤醒操作。
- 在Android系统中,当应用进行视图的刷新渲染时,ViewRootImpl的scheduleTraversals方法中,用到了同步消息屏障。这里使用的目的是:优先保障视图的刷新逻辑的执行,避免UI线程执行其他同步任务导致的卡顿等问题。
- 当系统完成UI视图的渲染刷新后,会调用ViewRootImpl的unscheduleTraversals方法移除同步屏障。