美文网首页
Android Handler机制--02:同步屏障

Android Handler机制--02:同步屏障

作者: DarcyZhou | 来源:发表于2023-10-25 15:37 被阅读0次

    本文转载自:Android消息机制之同步屏障

    本文基于Android 10.0源码分析

    1.概述

      同步屏障,view绘制中用同步屏障的概念,在Android开发中非常容易被人忽略。因为这个概念在我们普通的开发中太少见了,很容易被忽略。大家经过上面的学习应该知道,线程的消息都是放到同一个MessageQueue里面,取消息的时候是互斥取消息,而且只能从头部取消息,而添加消息是按照消息的执行的先后顺序进行排序。

      那么问题来了,同一个时间范围内的消息,如果它是需要立刻执行的,那我们怎么办?按照常规的办法,我们需要等到队列轮询到我自己的时候才能执行,那岂不是黄花菜都凉了。所以,我们需要给紧急执行的消息一个绿色通道,这个绿色通道就是同步屏障的概念。

    2.源码实现

      屏障的意思即为阻碍,顾名思义,同步屏障就是阻碍同步消息,只让异步消息通过。如何开启同步屏障呢?如下而已:MessageQueue# postSyncBarrier( ),我们看看它的源码:

    // frameworks/base/core/java/android/os/MessageQueue.java
        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) {
                final int token = mNextBarrierToken++;
                final Message msg = Message.obtain(); // 从消息池中获取Message
                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) {
                        // 如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步消息里有时间小于T,
                        // 则prev也不为null
                        prev = p;
                        p = p.next;
                    }
                }
                // 根据prev是不是为null,将msg按照时间顺序插入到消息队列(链表)的合适位置
                if (prev != null) { // invariant: p == prev.nextmsg.next = p;
                    prev.next = msg;
                } else {
                    msg.next = p;
                    mMessages = msg;
                }
                return token;
            }
        }
    

    postSyncBarrier()方法前面有一大段注释,大概意思是该方法会往MessageQueue中插入一条同步屏障message,遇到该message后,MessageQueue中的同步消息会延迟执行,直到通过调用removeSyncBarrier()方法移除同步屏障;而异步消息有豁免权,可以正常执行。

      可以看到,Message对象初始化的时候并没有给target赋值。因此,target == null的来源就找到了。上面消息的插入也做了相应的注释。这样,一条target == null消息就进入了消息队列。

      那么,开启同步屏障后,所谓的异步消息又是如何被处理的呢?如果对消息机制有所了解的话,应该知道消息的最终处理是在消息轮询器Looper#loop()中,而loop()循环中会调用MessageQueue# next() 从消息队列中进行取出消息。

     // frameworks/base/core/java/android/os/MessageQueue.java
        Message next() {
            // Return here if the message loop has already quit and been disposed.// This can happen if the application tries to restart a looper after quit// which is not supported.final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            //1.如果nextPo TimeoutMi is=-1,一直阻塞不会超时。
            //2.如果nextPo TimeoutMi is=0,不会阻塞,立即返回。
            //3.如果nextPo TimeoutMi is>0,最长阻塞nextPo TimeoutMi is毫秒(超时)
            int nextPollTimeoutMillis = 0;
            // 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; // 当前链表的头结点
                    // 关键:如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息
                    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());
                    }
    
                    if (msg != null) {
                        // 如果有消息需要处理,先判断时间有没有到,
                        // 如果没到的话设置一下阻塞时间,场景如常用的postDelay
                        if (now < msg.when) {
                            // Next message is not ready.  Set a timeout to wake up when it is ready.
                            //计算出离执行时间还有多久赋值给nextPollTimeoutMillis,    
                            //表示nativePo Once方法要等待nextPollTimeoutMillis时长后返回
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // Got a message.
                            mBlocked = false;//获取到消息
                            // 链表操作,获取msg并且删除该节点
                            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复位
                        nextPollTimeoutMillis = -1;
                    }
                    ...
            }
        }
    

    从上面可以看出,当消息队列开启同步屏障的时候 (即标识为msg.target == null) ,消息机制在处理消息的时候,优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。下面用示意图简单说明:

    Handler2-1.png

    如上图所示,在消息队列中有同步消息和异步消息(黄色部分)以及同步屏障消息(红色部分)。当执行到同步屏障消息的时候,msg_2和msg_M 这两个异步消息会被优先处理,而后面的msg_3等同步消息则不会被处理。这些同步消息需要先移除这个同步屏障,即调用removeSyncBarrier(),才能继续处理。

    3.应用场景

      Android 系统中更新UI就是使用同步屏障。在View更新时,draw、requestLayout、invalidate 等很多地方都调用了ViewRootImpl#scheduleTraversals(),如下:

    // frameworks/base/core/java/android/view/ViewRootImpl.java
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                //发送同步屏障消息
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                //发送异步消息
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    
    

    postCallback()最终走到了Choreographer#postCallbackDelayedInternal():

    // frameworks/base/core/java/android/view/Choreographer.java
    private void postCallbackDelayedInternal(int callbackType,
                Object action, Object token, long delayMillis) {
            if (DEBUG_FRAMES) {
                Log.d(TAG, "PostCallback: type=" + callbackType
                        + ", action=" + action + ", token=" + token
                        + ", delayMillis=" + 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 更新相关的消息优先级是最高的,这样系统就会优先处理这些异步消息。前面我们看到,同步屏障消息并不会自己移除,需要调用相关代码来移除同步屏障消息ViewRootImpl#unscheduleTraversals()。

    // frameworks/base/core/java/android/view/ViewRootImpl.java
        void unscheduleTraversals() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                //移除同步屏障消息
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
                mChoreographer.removeCallbacks(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            }
        }
    
    // frameworks/base/core/java/android/os/MessageQueue.java
        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;   //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信号到来时,绘制任务可以被及时执行,避免造成界面卡顿。

    4.总结

      同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用Handler.getLooper().getQueue().postSyncBarrier()并设置消息的setAsynchronous(true)时,target即为null,也就开启了同步屏障。当在消息轮询器Looper在loop()中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

    相关文章

      网友评论

          本文标题:Android Handler机制--02:同步屏障

          本文链接:https://www.haomeiwen.com/subject/shawbdtx.html