handler同步屏障

284 阅读3分钟

在Handler中,大致分为3种Message,分别是同步消息,异步消息和同步屏障,他们三者都是Message,只是成员变量有些区别.

Handler默认是同步消息.

1. 同步消息和异步消息

创建handler的时候,指定async异步消息参数, 会在消息进入队列的时候,设置消息类型为异步消息\

public Handler(boolean async) {
    this(null, async);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {\
    msg.target = this;
    //mAsynchronous为false,为同步消息
    if (mAsynchronous) {
        msg.setAsynchronous(true);\
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

2. 同步屏障

区分同步和异步消息的目的主要是因为同步屏障的存在 主要作用:屏蔽设置之后的队列中的同步消息,只会执行之后的异步消息。通常用在对用户响应及时性要求比较高的场景,例如:显示app界面

未命名文件 (1).png

官方解释:  * 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.\

MessageQueue遍历处理队列中的消息时,如果遇到同步屏障消息(msg.target=null),则之后的同步消息会被禁止直行,直到解除同步屏障

2.1 设置同步屏障的方法

mHandler.getLooper().getQueue().postSyncBarrier()//新建一个target为空的消息
mHandler.getLooper().getQueue().removeSyncBarrier()删除队列中Message的target为null的消息
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}
//新建一个target为空的消息
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++;
        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为null的消息

@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;
        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.");
        }
        //删除队列中Message的target为null的消息
        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);
        }
    }
}

2.2 消息处理策略

同步屏障的逻辑是在MessageQueue进行控制的

@UnsupportedAppUsage
Message next() {
    ...

    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        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());
            }
          ...
        }

       ...
    }
}

2.3 使用场景

屏幕需要绘图显示页面时,设置同步屏障,把绘图message设置成异步消息,这样

//ViewRootImpl.java
void scheduleTraversals() {
   if (!mTraversalScheduled) {
       mTraversalScheduled = true;
       //添加同步屏障,下面分析源码
       mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
       mChoreographer.postCallback(\
               Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
   }
}

poseCallback会在内部实现一个异步消息,添加到队列中去

Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);\
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);

doTraversal的删除同步屏障

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
      //移除消息屏障  
     mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        performTraversals();
        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}