android 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原理

下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/20154,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?