关于Handler的SyncBarrier解析

概念

SyncBarrier是同步分隔栏的意思,其实它是一个特殊的Message,它的targer为null。当消息队列遍历到这种消息类型的时候,它会跳过后面的同步Message(异步的Message会执行),这就是所谓的“同步分割栏”。

同步消息和异步消息

同步消息和异步消息的设置可以通过Handler的构造方法来指定:

public Handler(Callback callback, boolean async)  

也可以在Message中设置:

public void setAsynchronous(boolean async)  

同步消息和异步消息的区别就是SyncBarrier对它们的处理不同。

源码解析

private int postSyncBarrier(long when) {

        synchronized (this) {
            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;
        }
    }

这是创建了一个Message,它这里是没有指定其target属性的,后面是链表操作,通过比较when将其插入到合适的位置,最后返回token,该token用于移除SyncBarrier。

接着看消息遍历MessageQueue.next() :

synchronized (this) {  
    final long now = SystemClock.uptimeMillis();
    Message prevMsg = null;
    Message msg = mMessages;
        //1、判断是不是同步分隔栏
    if (msg != null && msg.target == null) {
        do {
            prevMsg = msg;
            msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());
    }

    if (msg != null) {
        if (now < msg.when) {
                  //2、msg执行的时间还没到,计算休眠时间
          nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
        } else {
            //3、取出消息
            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 {
        nextPollTimeoutMillis = -1;
    }
}

上面代码1标记的地方可以看到,msg.target==null的时候会进入循环:

do {  
            prevMsg = msg;
            msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());

这里它会遍历后面的Msg,如果下一个消息是同步消息的话会跳过,继续遍历下一个,直到遍历到异步消息或没有消息为止,所以这里同步分隔栏的功能就很明确了。

最后看是如何移除同步分隔栏的:MessageQueu.removeSyncBarrier:

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;
            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.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来遍历消息链表,如果删除动作改变了链表的头部,也意味着队列的最近唤醒时间应该被调整了,因此needWake会被设为true,以便代码下方可以走进nativeWake()。

应用

SyncBarrier的作用我们现在知道了,那通过它可以实现什么功能呢?
在ViewRootImpl中可以看到SyncBarrier的身影,为了让View能够有快速的布局和绘制,ViewRootImpl在开始measure和layout ViewTree时,会向主线程的Handler添加Barrier:

void scheduleTraversals() {  
        if (!mTraversalScheduled) {  
            mTraversalScheduled = true;  
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();  
            mChoreographer.postCallback(  
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);  
            scheduleConsumeBatchedInput();  
        }  
    } 

void unscheduleTraversals() {  
        if (mTraversalScheduled) {  
            mTraversalScheduled = false;  
            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);  
            mChoreographer.removeCallbacks(  
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);  
        }  

这样后续的消息队列中的同步的消息将不会被执行,以免会影响到UI绘制,但是只有异步消息才能被执行。

引用聊一聊Android的消息机制