从onVsync()说起,DisplayEventReceiver在视图渲染中扮演什么角色?

3,073 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情

接下来会出三篇文章详细介绍,View视图如何经过一遍遍的流程渲染到界面上的,从ActivityonResume(),经过Choreographer处理,经过FrameDisplayEventReceiver处理,最终触发View的measure、layout、draw三大流程,将视图绘制到界面上。

历史文章

从onResume()分析,ViewRootImpl在视图渲染中扮演什么角色?

从postCallback()说起,Choreographer在视图渲染中扮演什么角色?

概述

上面一篇文章最后从postCallback()说起,Choreographer在视图渲染中扮演什么角色?我们最终讲到了FrameDisplayEventReceiver.run()方法,这个方法是收到底层脉冲信号后,onVsync()触发调用的,最终实现了View的三大渲染流程measure、layout、draw,接下来我们详细分析下。

FrameDisplayEventReceiver.run()开始分析

@Override
public void run() {
    mHavePendingVsync = false;
    doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}

继续往下走调用到doFrame()方法,这个方法逻辑比较多,我们精简下只关注其中最核心的:

void doFrame(long frameTimeNanos, int frame,
        DisplayEventReceiver.VsyncEventData vsyncEventData) {
    try {
        
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);

        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
                frameIntervalNanos);
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);

        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
    }
}

有很多的doCallbacks()方法调用,每次调用不同的CALLBACK_XXX类型 ,还记得我们通过Choreographer.postCallback()方法添加刷新回调的逻辑吗,马上看下:

image.png

我们添加的视图渲染回调类型为CALLBACK_TRAVERSAL,对应上面第四个doCallbacks()方法的调用:

void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {
        final long now = System.nanoTime();
        //1.
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }
    }
    try {
        //2.
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
            c.run(frameTimeNanos);
        }
    } finally {
        synchronized (mLock) {
            mCallbacksRunning = false;
            do {
                //3.
                final CallbackRecord next = callbacks.next;
                recycleCallbackLocked(callbacks);
                callbacks = next;
            } while (callbacks != null);
        }
    }
}

上面的代码就是已经精简之后的代码了,下面我们开始分析:

  1. 首先从mCallbackQueues数组取出类型为CALLBACK_TRAVERSAL的对象,大家记得吗,我们之前的视图渲染回调就是放在这个mCallbackQueues数组中的。

最后拿到的是一个CallbackRecord类型,我们看下这个类的结构体:

image.png

这个对象的next属性指向下一个CallbackRecord对象,所以这是一个单链表结构,而action属性就是我们传入的视图渲染Runnable回调对象;

  1. 调用CallbackRecord.run()方法开始执行:

对应的就是上图中的run()方法逻辑,其中当类型为CALLBACK_TRAVERSAL时,这个token对象是空的,所以会走下面的代码逻辑((Runnable)action).run()

这里顺便说下什么时候token等于FRAME_CALLBACK_TOKEN呢,当调用postFrameCallbackDelayed()方法时,就会设置为FRAME_CALLBACK_TOKEN

下面我们看下((Runnable)action).run()具体的逻辑,这个action对象的就是TraversalRunnable对象:

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

继续看下doTraversal()方法:

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        performTraversals();
    }
}

首先移除了消息屏障,毕竟视图渲染完毕之后就没必要添加消息屏障了,阻碍其他任务的执行;

其次调用了方法performTraversals(),这个方法就是最最关键的方法了,真正的实现了视图三大流程measure、layout、draw的调用:

void performTranversals() {
    performMeasure();
    performLayout();
    performDraw();
}

这个方法600多行代码,找出上面三行关键代码是真的不容易,根据方法名就可以知道,分别代表视图的三大渲染流程measure、layout、draw分发执行;

  1. 结束上面的渲染执行流程后,调用方法recycleCallbackLocked()回收当前执行的CallbackRecord对象:
private void recycleCallbackLocked(CallbackRecord callback) {
    callback.action = null;
    callback.token = null;
    callback.next = mCallbackPool;
    mCallbackPool = callback;
}

最后放到了缓存对象mCallbackPool中。

分析完毕。

其他注意事项

  1. 注册底层脉冲信号之后想要,想要继续接受后续的脉冲信号,需要重新注册,也就是说你注册一次接下来只会生效一次,想要继续接受脉冲信号,那就继续注册;

  2. 借助postFrameCallbackDelayed()方法我们可以实现一个简单的帧率监听,比如1s中是否刷新了60次,像属性动画的播放就是依靠该方法注册监听回调实现的;

总结

本篇文章主要分析了收到底层脉冲信号后,从DisplayEventReceiver.onVsync()开始一步步怎么触发界面的绘制渲染流程的,结合其他的两篇文章观看,相信你会对整个View的上屏流程有一个更加清晰的认知,希望这三篇文章能对你有所帮助。