Android消息循环的同步屏障机制及UI渲染性能的提升(Android Q)

我们在 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;
        }
    }
逻辑解析:
  1. postSyncBarrier方法调用了有参的postSyncBarrier方法,传递了一个执行时间的参数。
  2. 在同步代码块中,执行添加逻辑。
  3. 首先通过mNextBarrierToken属性获取一个int类型的token,它也是该方法的返回值,用于移除屏障时使用。
  4. 获取了一个新的Message对象,注意这里没有设置target属性,正常的消息target属性不能为空。
  5. 根据when属性,将同步屏障消息添加到消息队列的合适位置。
  6. 最后返回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);
            }
        }
    }
逻辑解析:
  1. MessageQueue的removeSyncBarrier方法可以移除同步屏障,参数是添加消息屏障的返回值token,用于移除指定的消息屏障。
  2. removeSyncBarrier方法首先判断当前消息mMessages,是否是同步屏障消息,如果当前消息不是同步屏障消息,则遍历消息链表进行查找。
  3. 如果没有找到同步屏障消息,则抛出Error,否则继续执行。
  4. 如果同步屏障消息尚未执行,则删除该消息即可,不用执行唤醒消息循环。
  5. 否则,当前消息既是同步屏障消息,删除该消息后,需要根据条件,进一步判断是否需要唤醒消息循环。
  6. 最后,如果需要唤醒消息循环,并且当前消息循环未执行退出操作,则调用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系统中的使用,我们来简单总结下:

  1. 在MessageQueue的next方法中,获取当前执行的消息时,首先对消息进行了判断,如果当前消息是同步屏障消息,则屏蔽后续所有同步消息的执行,异步消息不受影响。
  2. 同步屏障消息对象的target属性是null,也就是没有持有Handler对象的引用。
  3. 同步屏障的消息可以通过MessageQueue的postSyncBarrier方法进行添加,添加时,不需要进行消息循环的唤醒操作。
  4. MessageQueue的postSyncBarrier方法的返回值是一个int类型的token,用于移除屏障时使用。
  5. 同步屏障添加后,并不能自动移除,需要调用系统接口手动移除。
  6. MessageQueue的removeSyncBarrier方法可以移除同步屏障,参数是添加消息屏障的返回值token。
  7. 如果同步屏障消息尚未执行,则删除该消息即可,不用执行唤醒消息循环。
  8. 当前消息既是同步屏障消息,删除该消息后,需要根据条件,进一步判断是否需要唤醒消息循环,如果需要唤醒消息循环,并且当前消息循环未执行退出操作,则调用nativeWake方法执行唤醒操作。
  9. 在Android系统中,当应用进行视图的刷新渲染时,ViewRootImpl的scheduleTraversals方法中,用到了同步消息屏障。这里使用的目的是:优先保障视图的刷新逻辑的执行,避免UI线程执行其他同步任务导致的卡顿等问题。
  10. 当系统完成UI视图的渲染刷新后,会调用ViewRootImpl的unscheduleTraversals方法移除同步屏障。

猜你喜欢

转载自blog.csdn.net/u011578734/article/details/106282309
Q A
q