渲染机制(二):Choreographer 源码解析

一、概述

背景:

在引入 Vsync 之前的Android 系统,系统渲染一帧是通过Message来实现的,Msg中间没有间隔(即上一帧绘制完,下一帧的Msg紧接着就开始被处理)。但由于Msg的消费时长存在变化,这直接导致了帧率不稳定(即1s内消费Msg的个数不稳定)。从而产生了Jank现象,画面撕裂等问题。

为了优化这些问题,Android 在4.1之后引入了 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 这一套机制,保证了 Android App 可以以一个稳定的帧率运行(60fps),减少帧率波动带来的不适感。

本文我们来具体探究一下 Choreographer 的功能。

源码版本: Android 29

关联文章:


二、Choreographer

2.1 Choreographer的作用

Android 系统在4.1引入 Choreographer 的目的是为了配合 Vsync,给上层 App 的渲染提供一个稳定的 Message 处理的时机。因此 Choreographer 在 Android 渲染链路中起到一个承上启下的作用。

  • 承上: 负责接收和处理 App(ViewRootImpl) 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。
    • 如:集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等。
  • 启下: 负责请求和接收 Vsync 信号。
    • 请求 Vsync:FrameDisplayEventReceiver.scheduleVsync()
    • 接收 Vsync 事件回调:FrameDisplayEventReceiver.onVsync()

接下来我们看下Choreographer在收到一个Vsync信号后执行的绘制任务。图片来源:Android 基于 Choreographer 的渲染机制详解

在这里插入图片描述

2.2 Choreographer 的工作流程

  1. Choreographer 的初始化
    1. 初始化 FrameHandler,绑定 Looper。
    2. 初始化 FrameDisplayEventReceiver,与 SurfaceFlinger 建立通信用于接收和请求 Vsync。
    3. 初始化 CallBackQueues。
  2. SurfaceFlinger 的 appEventThread 唤醒发送 Vsync ,Choreographer 回调。FrameDisplayEventReceiver.onVsync , 进入 Choreographer 的主处理函数 doFrame。
  3. Choreographer.doFrame 计算掉帧逻辑。
  4. Choreographer.doFrame 处理 input 类型的 callback。
  5. Choreographer.doFrame 处理 animation 类型的 callback。
  6. Choreographer.doFrame 处理 insets animation 类型的 callback。
  7. Choreographer.doFrame 处理 traversal 类型的 callback。
    1. traversal-draw 中 UIThread 与 RenderThread 同步数据。
  8. Choreographer.doFrame 处理 Choreographer 的第五个 callback : commit。
  9. RenderThread 处理绘制命令,将处理好的绘制命令发给 GPU 处理。
  10. 调用 swapBuffer 提交给 SurfaceFlinger 进行合成。

三、源码解析

上面我们简单介绍了一下 Choreographer 的作用,以及Choreographer在接收到Vsync信号后触发的任务执行流程,下面我们具体看看Choreographer的代码实现。

3.1 App侧的渲染流程

Window系列 (一) — WindowManager 详解 一文中,我们介绍了 ViewRootImpl.scheduleTraversals() 内通过 Choreographer.postCallback(FrameCallback) 方法设置监听来触发回调逻辑。当 Choreographer 接收到 Vsync 信号时,会触发 TraversalRunnable 任务,最终执行 doTraversal() 方法进行View树的 measure、layout、draw的流程。

下图是绘制一帧界面的完整流程图:
在这里插入图片描述


在分析 Choreographer.postCallback(FrameCallback) 方法之前,我们先来看下 Choreographer 的初始化逻辑。

3.2 Choreographer初始化

// Thread local storage for the SF choreographer.
private static final ThreadLocal<Choreographer> sSfThreadInstance =
        new ThreadLocal<Choreographer>() {
  	@Override
    protected Choreographer initialValue() {
    	// 获取当前线程的Looper
        Looper looper = Looper.myLooper();
        //...
        return new Choreographer(looper, VSYNC_SOURCE_SURFACE_FLINGER);
    }
};

private Choreographer(Looper looper, int vsyncSource) {
    mLooper = looper;
    // 1.初始化FrameHandler
    mHandler = new FrameHandler(looper);
    // 2.初始化DisplayEventReceiver,用于接收Vsync信号。
    mDisplayEventReceiver = USE_VSYNC
            ? new FrameDisplayEventReceiver(looper, vsyncSource)
            : null;
    // 记录最近一次收到Vsync信号的时间,单位:纳秒。
    mLastFrameTimeNanos = Long.MIN_VALUE;
	// 定义每一帧的时间长度,如果每秒60Hz,则时间长度为16.66ms。
    mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
	// 存放input、animation、traversal等事件的callback回调。
    mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
    for (int i = 0; i <= CALLBACK_LAST; i++) {
        mCallbackQueues[i] = new CallbackQueue();
    }
    // b/68769804: For low FPS experiments.
    setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
}

FrameHandler

private final class FrameHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_DO_FRAME: //开始渲染下一帧的操作
                doFrame(System.nanoTime(), 0);
                break;
            case MSG_DO_SCHEDULE_VSYNC: //请求Vsync信号 
                doScheduleVsync();
                break;
            case MSG_DO_SCHEDULE_CALLBACK: //处理Callback
                doScheduleCallback(msg.arg1);
                break;
        }
    }
}

小结:

  1. Choreographer 是个单例,每个线程独有(即线程间不共享)。
  2. 初始化Msg处理的Handler:FrameHandler。
  3. 初始化Vsync信号的接收器:FrameDisplayEventReceiver。
  4. 初始化 input、animation、traversal等事件的callback回调队列(mCallbackQueues)。

3.3 Choreographer.postCallback()

触发Choreographer.postCallback()的时机:

ViewRootImpl.scheduleTraversals() 内通过 Choreographer.postCallback(FrameCallback) 方法设置监听来触发回调逻辑。

// ViewRootImpl.class
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 这里传入CALLBACK_TRAVERSAL的callback类型。
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, 
        	mTraversalRunnable, null);
        // ...
    }
}

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
    	// 执行测量、布局、绘制的逻辑。
        doTraversal();
    }
}

通过postCallback注册Callback:
Choreographer.postCallback()
-> Choreographer.postCallbackDelayed()
-> 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;
        // mCallbackQueues是一个数组+链表的结构。
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
		// 调用postCallback方法时,delayMillis传入0,所以dueTime == now成立。
        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);
        }
    }
}

发起Vsync请求:
Choreographer.scheduleFrameLocked()
-> Choreographer.scheduleVsyncLocked()
-> DisplayEventReceiver.scheduleVsync()

private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {
    	// 避免在同一帧的时间区间内多次触发此逻辑(即每16.66ms内只会执行一次)。
        mFrameScheduled = true;
        if (USE_VSYNC) {
        	// 收发Vsync在同一个线程(即Looper相同),则直接执行,否则通过Handler进行线程间切换。
            if (isRunningOnLooperThreadLocked()) {
                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);
            Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, nextFrameTime);
        }
    }
}

private void scheduleVsyncLocked() {
	// 通过DisplayEventReceiver发送Vsync信号的请求。
    mDisplayEventReceiver.scheduleVsync();
}

小结:

  1. 通过 Choreographer.postCallback() 方法设置回调,回调中执行测量、布局、绘制等逻辑。
  2. 当完成第1步的Callback回调设置后,接下来会调用 DisplayEventReceiver.scheduleVsync() 方法来发送Vysnc信号的请求。

3.4 FrameDisplayEventReceiver

前面我们提到,FrameDisplayEventReceiver是一个Vsync信号的接收器,它继承自DisplayEventReceiver,里面主要有三个方法比较重要,分别是:

  • 发送接收Vsync的请求:DisplayEventReceiver.scheduleVsync()
  • 接收Vsync信号:DisplayEventReceiver.dispatchVsync() -> DisplayEventReceiver.onVsync()
  • 接收Vsync信号后具体的渲染:FrameDisplayEventReceiver.run()

发送Vsync信号的请求: DisplayEventReceiver.scheduleVsync()

// DisplayEventReceiver.class
public void scheduleVsync() {
	// mReceiverPtr为Native层接收数据对象的指针
	nativeScheduleVsync(mReceiverPtr);
}

private static native void nativeScheduleVsync(long receiverPtr);

接收Vsync信号:
DisplayEventReceiver.dispatchVsync()
-> DisplayEventReceiver.onVsync()

// DisplayEventReceiver.class
// Called from native code.
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
    onVsync(timestampNanos, physicalDisplayId, frame);
}

// FrameDisplayEventReceiver.class
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
    long now = System.nanoTime();
    if (timestampNanos > now) {
        timestampNanos = now;
    }

    if (mHavePendingVsync) {
        Log.w(TAG, "Already have a pending vsync event.  There should only be one at a time.");
    } else {
        mHavePendingVsync = true;
    }
	// 记录接收到Vsync信号的时间戳,用于在doFrame方法中计算掉帧逻辑。
    mTimestampNanos = timestampNanos;
    mFrame = frame;
    // 注意这里给Msg设置了Callback,所以最终在消费Msg时,会默认执行这个callback,即FrameDisplayEventReceiver.run()方法。
    Message msg = Message.obtain(mHandler, this);
   	// 这里开启了Msg的同步消息屏障,可以提升渲染类消息的消费速度。
    msg.setAsynchronous(true);
    // 这里发送消息。
    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

// FrameDisplayEventReceiver.class
public void run() {
	// 到这里才消费上面发送的消息。
	
    mHavePendingVsync = false;
    // doFrame内会做一些丢帧的记录,已经callback的消费。
    doFrame(mTimestampNanos, mFrame);
}

小结:

  1. DisplayEventReceiver.dispatchVsync() 方法接收到 Vsync 信号后,通过Handler发送消息的方式来往下传递,最终执行 Choreographer.doFrame() 里面的逻辑。
  2. 从Handler发送第1步消息开始到该消息被消费,这部分的时间往往依赖与这段时间内MQ中的Msg执行情况,如果这部分时间比较长,就可能会导致丢帧的情况。
  3. 第2步中提到当前的Msg从发送到最终消费之间存在空隙,所以 onVsync() 方法会向下传递接收到 Vsync信号的时间戳,用于在Choreographer.doFrame() 方法中计算掉帧的逻辑。

3.5 Choreographer.doFrame()

// Choreographer.class
void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        if (!mFrameScheduled) {
            return; // no work to do
        }
        long intendedFrameTimeNanos = frameTimeNanos;
        // 记录当前的时间。
        startNanos = System.nanoTime();
        // 计算从App端接收到Vsync信号开始到执行doFrame方法的时间开销。
        final long jitterNanos = startNanos - frameTimeNanos;
        // 差值>=一帧的时间周期(16.6ms)
        if (jitterNanos >= mFrameIntervalNanos) {
        	// 计算出丢了多少帧。
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            // 计算出当前时间距离最近一帧开始时间的偏移量。
            final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
            // 将
            frameTimeNanos = startNanos - lastFrameOffset;
        }
		//...
        if (mFPSDivisor > 1) { //mFPSDivisor默认是1
            long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
            if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                scheduleVsyncLocked();
                return;
            }
        }

        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
        // 还原状态,在scheduleFrameLocked方法中设置为true.
        mFrameScheduled = false;
        // 将最近的一帧开始时间赋值给它。
        mLastFrameTimeNanos = frameTimeNanos;
    }

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
		
		// 回调input类型的callback
        mFrameInfo.markInputHandlingStart();
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
		// 回调animation类型的callback
        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
		// 回调traversal类型的callback,我们在前面注册的监听回调就是在这里触发的。
        mFrameInfo.markPerformTraversalsStart();
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
		// 回调commit类型的callback
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } finally {
        AnimationUtils.unlockAnimationClock();
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

小结:

Choreographer.doFrame() 方法就做了2件事:

  1. 计算掉帧逻辑。
  2. 回调各种callback:input、animation、insets animation、traversal、commit。

3.6 Choreographer.doCallbacks()

void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {
        final long now = System.nanoTime();
        // 1.返回当前这一帧需要消费的callback回调,因为CallbackQueues是一个链表,所以只要返回头节点即可。
        // 1.1 先根据callbackType类型从mCallbackQueues获取对应的链表数据。
        // 1.2 传入当前的时间作为参考点,从头开始遍历,找到第1个时间晚于当前时间的callback,将它设置为新的头结点,并断开这个新头结点与前一个节点的连接,并返回之前的头结点。
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }
        mCallbacksRunning = true;

        if (callbackType == Choreographer.CALLBACK_COMMIT) {
            final long jitterNanos = now - frameTimeNanos;
            Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
            if (jitterNanos >= 2 * mFrameIntervalNanos) {
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
                        + mFrameIntervalNanos;
                frameTimeNanos = now - lastFrameOffset;
                mLastFrameTimeNanos = frameTimeNanos;
            }
        }
    }
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
        // 2.遍历指定callbackType类型的callback链表,依次进行回调。
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
        	// 这里触发CallbackRecord.run(),在其内部真正触发我们设置的callback回调。
            c.run(frameTimeNanos);
        }
    } finally {
        synchronized (mLock) {
            mCallbacksRunning = false;
            do {
            	// 3.对触发过callback回调的对象进行回收。
                final CallbackRecord next = callbacks.next;
                recycleCallbackLocked(callbacks);
                callbacks = next;
            } while (callbacks != null);
        }
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

小结:

Choreographer.doCallbacks() 方法主要做了3件事:

  1. 返回当前这一帧需要消费的callback回调,因为CallbackQueues是一个链表,所以只要返回头节点即可。
  2. 遍历上一步返回的头结点,触发对应的callback回调。
  3. 对触发过callback回调的对象进行回收。

四、总结

  1. Choreographer 是线程单例的,而且必须要和一个 Looper 绑定,因为其内部有一个 Handler 需要和 Looper 绑定,一般是 App 主线程的 Looper 绑定

  2. DisplayEventReceiver 是一个 abstract class,其 JNI 的代码部分会创建一个IDisplayEventConnection 的 Vsync 监听者对象。这样,来自 AppEventThread 的 VSYNC 中断信号就可以传递给 Choreographer 对象了。当 Vsync 信号到来时,DisplayEventReceiver 的 onVsync 函数将被调用。

  3. DisplayEventReceiver 还有一个 scheduleVsync 函数。当应用需要绘制UI时,将首先申请一次 Vsync 中断,然后再在中断处理的 onVsync 函数去进行绘制。

  4. Choreographer 定义了一个 FrameCallback interface,每当 Vsync 到来时,其 doFrame 函数将被调用。这个接口对 Android Animation 的实现起了很大的帮助作用。以前都是自己控制时间,现在终于有了固定的时间中断。

  5. Choreographer 的主要功能是,当收到 Vsync 信号时,去调用使用者通过 postCallback 设置的回调函数。目前一共定义了五种类型的回调,它们分别是:

    • CALLBACK_INPUT : 处理输入事件处理有关
    • CALLBACK_ANIMATION : 处理 Animation 的处理有关
    • CALLBACK_INSETS_ANIMATION : 处理 Insets Animation 的相关回调
    • CALLBACK_TRAVERSAL : 处理和 UI 等控件绘制有关
    • CALLBACK_COMMIT : 处理 Commit 相关回调,主要是是用于执行组件 Application/Activity/Service 的 onTrimMemory,在 ApplicationThread 的 scheduleTrimMemory 方法中向 Choreographer 插入的;另外这个 Callback 也提供了一个监测一帧耗时的时机。

说明: 第一步初始化完成后,后续就会在步骤 2-9 之间循环。


五、参考

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值