1、Message
Android中的Message分为三种:
- 同步消息
- 异步消息
- 同步屏障消息
它们都是Message,只是成员变量有些区别。
一般我们通过 Handler 发送消息(如调用Handler.sendMessage(@NonNull Message msg)
等),最终都会调用 Handler.enqueueMessage()
让消息入队,如下:
java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这里msg.target
会被赋值为this
, 而 this
即为我们的 Handler 对象,并且Message的成员变量 mAsynchronous
默认为 false
,这种消息是同步消息,我们一般发送的消息都是同步消息。
相对应也应该有异步消息吧?的确,还有一种很容易被忽略的 异步消息,因为除了系统源码外,我们一般很少会使用异步消息。那么,如何发送一个异步消息呢?
简单来说有两种方式。
一种是直接设置消息为异步的:
java
Message msg = mHandler.obtainMessage();
msg.setAsynchronous(true);
mMyHandler.sendMessage(msg);
还有一种需要用到 Handler 的构造方法,不过该方法已被标记为@hide
了,普通应用无法使用:
java
public Handler(boolean async) {
this(null, async);
}
但是如果没有同步屏障,异步消息与同步消息的执行并没有什么区别。
2、同步屏障
同步屏障究竟有什么作用?
同步屏障为handler消息机制提供了一种优先级策略,让异步消息的优先级高于同步消息。如何开启同步屏障呢?通过MessageQueue.postSyncBarrie()
可以开启同步屏障:
java
/**
* Posts a synchronization barrier to the Looper's message queue.
*
* Message processing occurs as usual until the message queue encounters the
* synchronization barrier that has been posted. When the barrier is encountered,
* later synchronous messages in the queue are stalled (prevented from being executed)
* until the barrier is released by calling {@link #removeSyncBarrier} and specifying
* the token that identifies the synchronization barrier.
*
* This method is used to immediately postpone execution of all subsequently posted
* synchronous messages until a condition is met that releases the barrier.
* Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
* and continue to be processed as usual.
*
* This call must be always matched by a call to {@link #removeSyncBarrier} with
* the same token to ensure that the message queue resumes normal operation.
* Otherwise the application will probably hang!
*
* @return A token that uniquely identifies the barrier. This token must be
* passed to {@link #removeSyncBarrier} to release the barrier.
*
* @hide
*/
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
postSyncBarrier()
方法前面有一大段注释,大概意思是该方法会往MessageQueue中插入一条同步屏障message,遇到该message后,MessageQueue中的同步消息会延迟执行,直到通过调用removeSyncBarrier()方法移除同步屏障;而异步消息有豁免权,可以正常执行。
java
// The next barrier token.
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
//这里有解释:同步屏障是一个target为null并且arg1为这个token的消息
@UnsupportedAppUsage
private int mNextBarrierToken;
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) {
final int token = mNextBarrierToken++; //为token赋值
final Message msg = Message.obtain();
msg.markInUse();
//这里初始化Message对象的时候没有给target赋值, 即target==null
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;
}
}
//将msg插入消息队列
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到,Message 对象初始化的时候并没有给 target
赋值,因此同步屏障消息的target == null
。
那么插入同步屏障消息后,异步消息是如何被优先处理的呢?
如果对消息机制有所了解的话,应该知道消息的最终处理是在消息轮询器Looper.loop()
中,而loop()
循环中会调用MessageQueue.next()
从消息队列中取消息,来看看关键代码:
java
@UnsupportedAppUsage
Message next() {
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
Message msg = mMessages;
//如果msg.target为null,是一个同步屏障消息,则进入这个判断
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
//先执行do,再执行while,遇到同步消息会跳过,遇到异步消息退出循环
//即取出的msg为该屏障消息后的第一条异步消息,屏障消息不会被取出
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
//如果消息的处理时间大于当前时间则等待
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
//处理消息
mBlocked = false;
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 {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
}
从上面的代码可以看出,当执行到同步屏障消息(即target == null
的Message)时,消息机制优先处理异步消息。由于代码中先执行do再执行while
,取出的msg为该屏障消息后的第一条异步消息,而屏障消息不会被取出。
下面用示意图简单说明:
如上图所示,在消息队列中有同步消息和异步消息(黄色部分)以及同步屏障消息(红色部分)。当执行到同步屏障消息的时候,msg_2 和 msg_M 这两个异步消息会被优先处理,而msg_3 等同步消息则要等异步消息处理完后再处理。
3、同步屏障的使用场景
在日常的应用开发中,我们很少会用到同步屏障。Android 系统源码中UI更新就是使用的同步屏障,这样会优先处理更新UI的消息,尽量避免造成界面卡顿。
UI更新都会调用ViewRootImpl.scheduleTraversals()
,其代码如下:
java
public final class ViewRootImpl{
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//往队列中插入同步屏障消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//往队列中插入异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
}
Choreographer.postCallback()
最终调用了postCallbackDelayedInternal()
方法:
java
public final class Choreographer {
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);//异步消息
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
}
这里就往队列中插入了同步屏障消息,然后又插入了异步消息,UI 更新相关的消息就可以优先得到处理。
前面代码中我们看到,同步屏障消息并不会自己移除,所以需要调用相关代码来移除同步屏障消息,让同步消息可以正常执行。Android源码中在执行绘制流程之前执行了移除同步屏障的代码:
java
public final class ViewRootImpl{
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障消息
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
//里面执行了绘制的3大流程
performTraversals();
...
}
}
}
public final class MessageQueue {
/**
* Removes a synchronization barrier.
*
* @param token The synchronization barrier token that was returned by
* {@link #postSyncBarrier}.
*
* @throws IllegalStateException if the barrier was not found.
*
* @hide
*/
@UnsupportedAppUsage
@TestApi
public void removeSyncBarrier(int 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;
//找到同步屏障消息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的下一条消息
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);
}
}
}
}
}
通过以上的分析,对于同步屏障的原理已经了解了吧。在绘制流程中使用同步屏障,保证了在VSYNC信号到来时,绘制任务可以及时执行,避免造成界面卡顿。