阅读 212

Choreographer工作逻辑总结

为了更好的理解使用Choreographer监控App FPS的原理,本文先来梳理一下Choreographer的工作原理。

Choreographer主要是用来协调动画、输入和绘制事件运行的。它通过接收Vsync信号来调度应用下一帧渲染时的动作。

对 Vsync 信号的监听

Choreographer会通过Choreographer.FrameDisplayEventReceiver来监听底层HWC触发的Vsync信号:

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {

    }
}
复制代码

Vsync信号可以理解为底层硬件的一个系统中断,它每16ms会产生一次。上面onVsync()的每个参数的意义为:

timestampNanos : Vsync信号到来的时间, 这个时间使用的是底层JVM nanoscends -> System.nanoTime

builtInDisplayId : 此时SurfaceFlinger内置的display id

frame : 帧号,随着onVsync的回调数增加

onVsync的回调逻辑

onVsync什么时候会调用呢?

Choreographer.scheduleVsyncLocked()会请求下一次Vsync信号到来时回调FrameDisplayEventReceiver.onVsync()方法:

private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();
}
复制代码

Vsync信号到来时执行callback

设置callback

Choregrapher提供了下面方法设置callback:

public void postCallback(int callbackType, Runnable action, Object token) 
public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis)
public void postFrameCallback(FrameCallback callback)
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) 
复制代码

Choregrapher中存在多个Callback Queue, 常见的Callback Queue的类型有:

Choreographer.CALLBACK_INPUT       输入事件,比如键盘
Choreographer.CALLBACK_ANIMATION   动画
Choreographer.CALLBACK_TRAVERSAL   比如`ViewRootImpl.scheduleTraversals, layout or draw`
Choreographer.CALLBACK_COMMIT           
复制代码

上面4个事件会在一次Vsync信号到来时依次执行。

callback的执行逻辑

ViewRootImpl.scheduleTraversals为例:

void scheduleTraversals() {
    ...
    mChoreographer.postCallback(
        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
复制代码

即给Choregrapher提交了一个Choreographer.CALLBACK_TRAVERSAL类型的callback去执行。

postCallback()里面的具体执行逻辑就不分析了,这里直接说一下关键逻辑:

  1. 切换到 Choregrapher创建时所在的线程去调用scheduleFrameLocked()方法,设置mFrameScheduled = true
  2. 调用scheduleVsyncLocked请求下一次Vsync信号回调
  3. FrameDisplayEventReceiver.onVsync()会生成一个消息,然后发送到Choreographer.mHander的消息队列
  4. Choreographer.mHander取出上面onVsync中发送的消息,执行Choreographer.doFrame()方法,doFrame()中判断mFrameScheduled是否为true,如果为true的话就上面四种callback

综上所述Choreographer的工作原理如下图:

doFrame() 的时间参数

我们来看一下这个方法(主要关注一下时间参数):

void doFrame(long frameTimeNanos, int frame) {

    final long startNanos;
    if (!mFrameScheduled) {
        return; // no work to do
    }

    long intendedFrameTimeNanos = frameTimeNanos; 
    startNanos = System.nanoTime();

    final long jitterNanos = startNanos - frameTimeNanos;

    if (jitterNanos >= mFrameIntervalNanos) {  //16ms
        final long skippedFrames = jitterNanos / mFrameIntervalNanos;
        final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
        frameTimeNanos = startNanos - lastFrameOffset;      
    }

    mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
    mFrameScheduled = false;
    mLastFrameTimeNanos = frameTimeNanos;

    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
复制代码

解释一下上面一些时间相关参数的含义:

intendedFrameTimeNanos: 预计这一帧开始渲染的时间

frameTimeNanos: 这一帧真正开始渲染的时间。在 startNanos - frameTimeNanos < mFrameIntervalNanos,其实就等于intendedFrameTimeNanos

jitterNanos: 真正渲染时间点和预计渲染时间点之差

mFrameIntervalNanos: 每一帧期望渲染的时间, 固定为16ms

skippedFrames : jitterNanos总共跳过了多少帧。

mLastFrameTimeNanos : 上一次渲染一帧的时间点

jitterNanos > mFrameIntervalNanos 在什么时候会成立呢?

其实就是我们常说的丢帧: 比如我们连续提交了两个Choreographer.CALLBACK_TRAVERSAL callback。如果一个callback的执行时间大于16ms,那么就会造成:

startNanos - frameTimeNanos > mFrameIntervalNanos(16ms)
复制代码

doCallback(int callbackType, long frameTimeNanos)

这个方法的逻辑并不复杂 : 获取callbackType对应的Callback Queue, 取出这个队列中已经过期的calllback进行执行。

void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
    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));
        }
        c.run(frameTimeNanos);
    }
}
复制代码

Choreographer与主线程消息循环的关系

上面我们已经知道onVsync把要执行doFrame的消息放入了Choreographer.mHander的消息队列。

这里Choreographer.mHander的消息队列其实就是主线程的消息,所以doFrame方法其实是由主线程的消息循环来调度的

我们看一下Choreographer实例化时的Looper:

private static final ThreadLocal<Choreographer> sThreadInstance =
        new ThreadLocal<Choreographer>() {
    @Override
    protected Choreographer initialValue() {
        Looper looper = Looper.myLooper();
        ...
        Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
        if (looper == Looper.getMainLooper()) {
            mMainInstance = choreographer;
        }
        return choreographer;
    }
};
复制代码

即取的是当前线程的Looper,所以donFrame()是在主线程的消息循环中调度的。

参考文章:

Android Choreographer 源码

Choreographer原理

后面会分析Tencent/matrix的实现原理,欢迎关注我的Android进阶计划看更多文章。

关注下面的标签,发现更多相似文章
评论