分析Handler之同步屏障机制与Android的屏幕刷新机制在源码中的应用

说到Handler的消息机制,相信大家谈起这个的时候,多多少少都会有所了解,甚至会说到,还比较熟悉吧!那笔者也自信一把,算是比较熟悉!!!
但是笔者在跟踪学习View的invalidate()requestLayout()方法的源码时,总是会遇到这样一段不甚知晓的代码:

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 关注点 1.通过MessageQueue#postSyncBarrier()设置Handler消息的同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 关注点 2.Choreographer 通过 postCallback 提交一个任务,mTraversalRunnable是要执行的回调
            // 有了同步屏障mTraversalRunnable就会被优先执行
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

关注点 1:
通过MessageQueue#postSyncBarrier() 设置Handler消息的同步屏障,有了同步屏障mTraversalRunnable就会被优先执行;
问题 1: 那么问题来了,什么是同步屏障呢?

关注点 2:
通过Choreographer#postCallback() 提交一个任务,mTraversalRunnable是要执行的回调任务。
问题 2: Choreographer#postCallback()只是提交一个任务吗?为何它提交一个任务,上面设置了同步屏障,它提交的任务会被优先执行?

问题 1:
首先来看一下什么是同步屏障?
同步屏障是 MeesageQueue 中一种特殊的机制,源码注释如下:

	/**
     * 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!
     */

英文注释的大概意思是: 在消息队列中遇到已经post进来的同步障碍之前,消息的处理是正常进行的。当遇到同步屏障时,消息队列中位于同步屏障后的同步消息将被暂停(阻止执行),直到通过调用 MessageQueue#removeSyncBarrier 并指定标识同步屏障的token来释放同步屏障,以确保消息队列恢复正常操作。否则,应用程序可能会挂起。

消息队列中的异步消息不受同步障碍的限制,可以继续像正常情况一样进行循环遍历并处理。

下面就来看一下消息队列MessageQueue#postSyncBarrier() 方法,追踪查看是如何插入同步屏障的。

MessageQueue#postSyncBarrier():

    public int postSyncBarrier() {
    	// 传入系统的当前时间,调用有参的postSyncBarrier()
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

继续调用 MessageQueue#postSyncBarrier(long when) 插入一个同步屏障,when 参数指定屏障插入的时间。
MessageQueue#postSyncBarrier(long when):

    private int postSyncBarrier(long when) {
    	// 加入一个新的同步屏障token
    	// 我们无需唤醒消息队列,因为同步屏障的目的就是为了暂停消息队列的
        // 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对象并插入到了消息链表中。乍一看好像没什么特别的,但是这里面有一个很大的不同点是该Message是没有给 target 赋值的。也就是说同步屏障是一个特殊的 Message,它没有对应的 Handler,它的插入逻辑和普通消息的插入是一样,但是这个插入不会唤醒 block 住的 poll 循环。

所以,从代码层面上来讲,同步屏障就是一个Message,一个target字段为空的Message。

而通常情况下,我们使用Handler向任务队列添加Message,Handler中发送消息的方法有post***、sendEmptyMessage***以及sendMessage***等,而这些最终都会调用enqueueMessage()方法。

Handler#enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis):

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        // msg的target赋值为当前发送消息的Handler自身
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

		// 如果是异步消息,Message通过调用setAsynchronous(true)方法,将该消息标记位异步消息
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

通过这个方法我们可以看到,由enqueueMessage()插入到消息队列的消息,消息msg的 target 字段都赋值为this,也就是发送该消息的Handler本身。

同步屏障插入到消息队列之后,我们知道消息队列是由Looper管理的,在Looper#loop() 方法中,开启一个死循环,不断的遍历消息队列。

Looper#loop():

    public static void loop() {
        final Looper me = myLooper();
		......省略代码
        // 获取 Looper 的 MessageQueue 对象
        final MessageQueue queue = me.mQueue;
		......省略代码
		// 开启死循环,不断的遍历消息队列
        for (;;) {
        	// 不断的调用 MessageQueue 的 next()方法返回一个待处理消息
            Message msg = queue.next(); // might block
			......省略代码
            try {
            	// msg.target指的就是发送该消息的Handler,所以调用的是Handler的dispatchMessage方法
                msg.target.dispatchMessage(msg);
                ......省略代码
            } catch (Exception exception) {
    		......省略代码
            } finally {
            ......省略代码
            }
            ......省略代码
            // 回收消息
            msg.recycleUnchecked();
        }
    }

Looper#loop() 方法只是开启消息队列的循环,要查看消息队列内部是如何取出一个待处理消息的,查看MessageQueue#next()方法。

MessageQueue#next():

Message next() {
        ......省略代码
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            // 该方法会持续 block,直到超时或者被 nativeWake 方法唤醒
            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是空的
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    // 如果当前队首消息是同步屏障消息(target==null),那么就循环寻找下一条异步消息并取出队列
            		// while 的判断条件,消息不为null,并且不是异步消息时,继续循环寻找下一条消息
                    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 继续调用 nativePollOnce 等待
                        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;
                }
				......省略代码
            }
			......省略代码
           nextPollTimeoutMillis = 0;
        }
    }

由此可见,同步屏障和异步消息通常是搭配使用的。看到这里,同步屏障是什么?童鞋们应该清楚了吧!

接下来针对 问题 2,可以大胆猜测一下,Choreographer 通过 postCallback 提交一个任务,那么是不是这个任务里面创建并发送了一个异步消息呢?

Choreographer是负责获取Vsync同步信号并控制App线程(主线程)完成图像绘制的类。

		// 这个任务里面创建并发送了一个异步消息呢?
        mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

问题 2: Choreographer#postCallback()只是提交一个任务吗?为何它提交一个任务,上面设置了同步屏障,它提交的任务会被优先执行?

还是从源码中找答案,跟踪查看
Choreographer#postCallback() 方法:

    /**
     * Posts a callback to run on the next frame.
     * 将回调放在下一帧调用
     */
    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }

该方法的注释说的很明白,将传进来的回调放到下一帧调用,该方法内部又调用了
Choreographer#postCallbackDelayed() 方法:

    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        ......省略代码
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }

方法内部又调用了
Choreographer#postCallbackDelayedInternal() 方法:

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
		......省略代码
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            // 将消息以当前的时间戳放进mCallbackQueue 队列里
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
            	// 如果没有设置消息延时,直接执行
                scheduleFrameLocked(now);
            } else {
            	// 有消息延时,但是最终依然会调用scheduleFrameLocked
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

将回调任务加入队列mCallbackQueues中,由于传入的delayMillis为0,所以接下来执行
Choreographer#scheduleFrameLocked() 方法:

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
				......省略代码
                // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                if (isRunningOnLooperThreadLocked()) {
                	// 如果当前线程是Choreographer的工作线程,我觉得就是主线程
                    scheduleVsyncLocked();
                } else {
                	// 否则发一条消息到主线程,并设置为异步消息
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    // 插到消息队列头部,这里可以理解为设置最高优先级
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }

如果运行在Looper线程上,则立即调度vsync;否则,通过Handler发一个异步消息到消息队列,最终也是到主线程处理,这个消息是异步消息,在先前同步屏障的作用下,会优先执行。那么接下来进入
Choreographer#scheduleVsyncLocked() 方法:

    @UnsupportedAppUsage
    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

继续进入
DisplayEventReceiver#scheduleVsync():

    /**
     * Schedules a single vertical sync pulse to be delivered when the next display frame begins.
     * 当下一个显示帧开始时,调度并传递一个单独的垂直同步脉冲。
     */
    @UnsupportedAppUsage
    public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
        	// 调用JNI方法nativeScheduleVsync请求注册vsync信号
            nativeScheduleVsync(mReceiverPtr);
        }
    }

nativeScheduleVsync() 是通过JNI调用Native层的代码,它会向SurfaceFlinger注册Vsync信号的监听,VSync信号由SurfaceFlinger实现并定时发送,当Vsync信号到来的时候就会回调DisplayEventReceiver#dispatchVsync()
DisplayEventReceiver#dispatchVsync():

    // Called from native code.
    @SuppressWarnings("unused")
    @UnsupportedAppUsage
    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
        onVsync(timestampNanos, physicalDisplayId, frame);
    }

在Choreographer中,mDisplayEventReceiver是FrameDisplayEventReceiver,且继承自DisplayEventReceiver,并重写了onVsync() 方法,所以下面需要进入到
FrameDisplayEventReceiver#onVsync():

        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            ......省略代码
            long now = System.nanoTime();
            if (timestampNanos > now) {
                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
                        + " ms in the future!  Check that graphics HAL is generating vsync "
                        + "timestamps using the correct timebase.");
                timestampNanos = now;
            }
			......省略代码
            mTimestampNanos = timestampNanos;
            mFrame = frame;
            // 构建一个消息,并携带一个回调任务,也就是FrameDisplayEventReceiver
            Message msg = Message.obtain(mHandler, this);
            // 设置该消息为异步消息
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }
        
        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }

FrameDisplayEventReceiver#onVsync() 方法,在特定的时刻发送消息,Message携带回调Runnable消息,而FrameDisplayEventReceiver实现Runnable,因此,最后在特定的时刻运行FrameDisplayEventReceiver的run方法,进而调用了
FrameDisplayEventReceiver#doFrame():

    @UnsupportedAppUsage
    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }
			......省略代码
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            final long jitterNanos = startNanos - frameTimeNanos;
            if (jitterNanos >= mFrameIntervalNanos) {
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                ......省略代码
                frameTimeNanos = startNanos - lastFrameOffset;
            }

            if (frameTimeNanos < mLastFrameTimeNanos) {
                ......省略代码
                scheduleVsyncLocked();
                return;
            }

            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    scheduleVsyncLocked();
                    return;
                }
            }

            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            // 恢复mFrameScheduled标志
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
        }

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);

            mFrameInfo.markPerformTraversalsStart();
            // 这个就是我们要找到的执行回调
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        ......省略代码
    }

该方法会计算当前时间与时间戳的间隔,其中入参代表上一个帧的时间,如果当前时间已经>=frameTimeNanos一个间隔,表示发生了跳帧,根据时间差值/帧间隔计算跳过帧数,重置frameTimeNanos为不发生挑帧时,最近的一个帧时间点。最后,设置mLastFrameTimeNanos值,记录上一帧的时间。
紧接着就是回调处理每个类型的Callbacks,触发CallbackRecord的run方法,CallbackRecord封装了任务action。对于CALLBACK_TRAVERSAL类型,最终会回调到 ViewRootImpl#TraversalRunnable 的run方法。
FrameDisplayEventReceiver#doCallbacks():

void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            final long now = System.nanoTime();
            // 设置同步屏障后,我们post进来一个任务消息,并加入到mCallbackQueues,这里需要根据类型和时间戳取出来
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;
            ......省略代码
        }
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "RunCallback: type=" + callbackType
                            + ", action=" + c.action + ", token=" + c.token
                            + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
                }
                // 从 mCallbackQueue 中取出CallbackRecord并按照时间戳顺序调用 mTraversalRunnable 的 run 方法
                c.run(frameTimeNanos);
            }
        } finally {
            synchronized (mLock) {
                mCallbacksRunning = false;
                do {
                    final CallbackRecord next = callbacks.next;
                    recycleCallbackLocked(callbacks);
                    callbacks = next;
                } while (callbacks != null);
            }
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

首先根据类型 callbackType 和时间戳取出来对应的CallbackRecord,CallbackRecord和链表的节点值定义相似,里面封装了任务action、下一个CallbackRecord的指向next等。然后按照时间戳顺序调用 mTraversalRunnable 的 run 方法,对于CALLBACK_TRAVERSAL类型,最终会回调到 ViewRootImpl#TraversalRunnable 的run方法。
ViewRootImpl#TraversalRunnable:

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
	
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            // 通过调用 removeSyncBarrier 方法移除同步屏障,传入 postSyncBarrier 返回的 token 作为参数
            // 标识需要移除哪个屏障,然后将该屏障消息会从队列中移除,以确保消息队列恢复正常操作,否则,应用程序可能会挂起
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
			// 到这里就是我们所有与屏幕刷新有关的源码分析的入口啦!
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

onVsync方法的执行执行流程图
贴一张FrameDisplayEventReceiver的onVsync方法执行流程图,便于理解。

分析了这么多,结合问题 1 和问题 2 总结一下:
我们知道,Android是基于消息机制的,每一个操作都是一个Message,如果我们想要主动去触发绘制的时候,消息队列中还有很多消息(同步或异步消息)没有被执行,那是不是意味着需要等到消息队列中的消息执行完成后,绘制消息才能被执行到呢?

有人可能会说,直接将Message的执行时间设置为当前时间就行了,当然可以,但是如果队列中有多个类似的Message,就不能保证你的Message能够在下次执行(就算是高优的任务,也不能打断正在执行的任务,所以只能是下次)。

针对这个情况,在查看源码时,我们发现系统源码在 Choreographer#scheduleFrameLocked 和 FrameDisplayEventReceiver#onVsync 中, 会把与绘制请求有关的Message设置成异步消息(msg.setAsynchronous(true)),再结合 MessageQueue#postSyncBarrier ,能够把我们的异步消息(也就是绘制消息)的优先级提到最高。

主线程的 Looper 在一直循环调用 MessageQueue 的 next 来取出队头的 Message 执行,当 Message 执行完后再去取下一个。当 next 方法在取 Message 时发现队头是一个同步屏障的消息时,就会去遍历整个队列,只寻找设置了异步标志的消息,如果有找到异步消息,那么就取出这个异步消息来执行,否则就让 next 方法陷入阻塞状态。

如果 next 方法陷入阻塞状态,那么主线程此时就是处于空闲状态的,也就是没在干任何事。

所以,如果队头是一个同步屏障的消息的话,那么在它后面的所有同步消息就都被拦截住了,直到这个同步屏障消息被移除,否则主线程就一直不会去处理同步屏障后面的同步消息,应用也将挂起;

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值