Loading

Choreographer原理

Android 系统在 VSYNC 信号的指引下,有条不紊地进行者每一帧的渲染、合成操作,使我们可以享受稳定帧率的画面。引入 VSYNC 之前的 Android 版本,渲染一帧相关的 Message ,中间是没有间隔的,上一帧绘制完,下一帧的 Message 紧接着就开始被处理。这样的问题就是,帧率不稳定,可能高也可能低,不稳定。

Choreographer 是 Android 4.1 新增的机制,主要是配合 VSYNC ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 VSYNC 到来的时候 ,系统通过对 VSYNC 信号周期的调整,来控制每一帧绘制操作的时机。目前大部分手机都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 VSYNC 的周期也设置为 16.6 ms,每个 16.6 ms,VSYNC 信号唤醒 Choreographer 来做 App 的绘制操作 ,这就是引入 Choreographer 的主要作用。

一、Choreographer 简介

Choreographer 通过 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 这一套从上到下的机制,保证了 Android App 可以以一个稳定的帧率运行(目前大部分是 60fps),减少帧率波动带来的不适感。

1、负责接收和处理 App 的各种更新消息和回调,等到 VSYNC 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等。

2、负责请求和接收 VSYNC 信号。接收 VSYNC 事件回调(通过 FrameDisplayEventReceiver.onVsync());请求 VSYNC(FrameDisplayEventReceiver.scheduleVsync())。

二、原理解析

1、初始化

Choreographer构造方法
private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        //初始化FrameHandler,与looper绑定
        mHandler = new FrameHandler(looper);
        // 初始化 DisplayEventReceiver(开启VSYNC后将通过FrameDisplayEventReceiver接收VSYNC脉冲信号)
        mDisplayEventReceiver = USE_VSYNC
                ? new FrameDisplayEventReceiver(looper, vsyncSource)
                : null;
        mLastFrameTimeNanos = Long.MIN_VALUE;
        // 计算一帧的时间,Android手机屏幕采用60Hz的刷新频率(这里是纳秒 ≈16000000ns 还是16ms)
        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
        // 初始化CallbackQueue(CallbackQueue中存放要执行的输入、动画、遍历绘制等任务)
        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));
    }
获取Choreographer实例
    public static Choreographer getInstance() {
        return sThreadInstance.get();
    }

    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };


可以看出构造的实例是与线程绑定的。

2、FrameHandler

    private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                // 如果启用VSYNC机制,当VSYNC信号到来时触发
                // 执行doFrame(),开始渲染下一帧的操作
                case MSG_DO_FRAME:
                    doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
                    break;
				// 请求VSYNC信号,例如当前需要绘制任务时
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                    break;
				// 处理 Callback(需要延迟的任务,最终还是执行上述两个事件)
                case MSG_DO_SCHEDULE_CALLBACK:
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }

Choreographer 的所有任务最终都会发送到该 Looper 所在的线程。

3、Choreographer 初始化链

在 Activity 启动过程,执行完 onResume() 后,会调用 Activity.makeVisible(),然后再调用到 addView(), 层层调用会进入如下方法:

点击查看代码
ActivityThread.handleResumeActivity(IBinder, boolean, boolean, String) (android.app) 
-->WindowManagerImpl.addView(View, LayoutParams) (android.view) 
   -->WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view) 
      -->ViewRootImpl.ViewRootImpl(Context, Display) (android.view) 
          public ViewRootImpl(Context context, Display display) { 
              ...... 
              mChoreographer = Choreographer.getInstance(); 
              ...... 
             }

4、FrameDisplayEventReceiver

VSYNC 的注册、申请、接收都是通过 FrameDisplayEventReceiver 这个类,FrameDisplayEventReceiver 是 DisplayEventReceiver 的子类, 有三个比较重要的方法:

  • onVsync():Vsync 信号回调,系统native方法会调用。
  • scheduleVsync():请求 Vsync 信号,当应用需要绘制时,通过 scheduledVsync() 方法申请 VSYNC 中断,来自 EventThread 的 VSYNC 信号就可以传递到 Choreographer。
  • run():执行 doFrame。

DisplayEventReceiver 是一个 abstract class,在 DisplayEventReceiver 的构造方法会通过 JNI 创建一个 IDisplayEventConnection 的 VSYNC 的监听者。

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;
        private VsyncEventData mLastVsyncEventData = new VsyncEventData();

        public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
            super(looper, vsyncSource, 0);
        }

        // TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
        // the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
        // for the internal display implicitly.
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
                VsyncEventData vsyncEventData) {
            try {
                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                    Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                            "Choreographer#onVsync " + vsyncEventData.id);
                }
                // Post the vsync event to the Handler.
                // The idea is to prevent incoming vsync events from completely starving
                // the message queue.  If there are no messages in the queue with timestamps
                // earlier than the frame time, then the vsync event will be processed immediately.
                // Otherwise, messages that predate the vsync event will be handled first.
                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;
                }

                if (mHavePendingVsync) {
                    Log.w(TAG, "Already have a pending vsync event.  There should only be "
                            + "one at a time.");
                } else {
                    mHavePendingVsync = true;
                }

                mTimestampNanos = timestampNanos;
                mFrame = frame;
                mLastVsyncEventData = vsyncEventData;
                Message msg = Message.obtain(mHandler, this);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }

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

可以看出,其最终是执行了run,然后调用doFrame绘制一帧。

5、Choreographer 处理一帧的逻辑

    void doFrame(long frameTimeNanos, int frame,
            DisplayEventReceiver.VsyncEventData vsyncEventData) {
        final long startNanos;
        final long frameIntervalNanos = vsyncEventData.frameInterval;
        try {
            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                        "Choreographer#doFrame " + vsyncEventData.id);
            }
            synchronized (mLock) {
                if (!mFrameScheduled) {
                    traceMessage
                            ("Frame not scheduled");
                    return; // no work to do
                }

                if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
                    mDebugPrintNextFrameTimeDelta = false;
                    Log.d(TAG, "Frame time delta: "
                            + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
                }
                //预期执行时间
                long intendedFrameTimeNanos = frameTimeNanos;
                //当前时间
                startNanos = System.nanoTime();
                final long jitterNanos = startNanos - frameTimeNanos;
                //超过的时间是否大于一帧的时间
                if (jitterNanos >= frameIntervalNanos) {
                    final long skippedFrames = jitterNanos / frameIntervalNanos;
                    //掉帧超过30帧就打印log
                    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 % frameIntervalNanos;
                    if (DEBUG_JANK) {
                        Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                                + "which is more than the frame interval of "
                                + (frameIntervalNanos * 0.000001f) + " ms!  "
                                + "Skipping " + skippedFrames + " frames and setting frame "
                                + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                    }
                    frameTimeNanos = startNanos - lastFrameOffset;
                }
                // 未知原因,居然小于最后一帧的时间,重新申请VSYNC信号
                if (frameTimeNanos < mLastFrameTimeNanos) {
                    if (DEBUG_JANK) {
                        Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                                + "previously skipped frame.  Waiting for next vsync.");
                    }
                    traceMessage("Frame time goes backward");
                    scheduleVsyncLocked();
                    return;
                }

                if (mFPSDivisor > 1) {
                    long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                    if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                        traceMessage("Frame skipped due to FPSDivisor");
                        scheduleVsyncLocked();
                        return;
                    }
                }

                mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
                        vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
                mFrameScheduled = false;
                mLastFrameTimeNanos = frameTimeNanos;
                
                mLastFrameIntervalNanos = frameIntervalNanos;
                mLastVsyncEventData = vsyncEventData;
            }

            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            //执行input任务
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);

            mFrameInfo.markAnimationsStart();
            //执行animation任务
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
            //执行Insert animation任务
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
                    frameIntervalNanos);

            mFrameInfo.markPerformTraversalsStart();
            //执行traversal任务,即测量绘制任务
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
            //执行commit任务
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        if (DEBUG_FRAMES) {
            final long endNanos = System.nanoTime();
            Log.d(TAG, "Frame " + frame + ": Finished, took "
                    + (endNanos - startNanos) * 0.000001f + " ms, latency "
                    + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
        }
    }

可以看到执行任务的顺序为:
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);

我们看一下doCalbacks方法:

    void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            // We use "now" to determine when callbacks become due because it's possible
            // for earlier processing phases in a frame to post callbacks that should run
            // in a following phase, such as an input event that causes an animation to start.
            final long now = System.nanoTime();
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;

            // Update the frame time if necessary when committing the frame.
            // We only update the frame time if we are more than 2 frames late reaching
            // the commit phase.  This ensures that the frame time which is observed by the
            // callbacks will always increase from one frame to the next and never repeat.
            // We never want the next frame's starting frame time to end up being less than
            // or equal to the previous frame's commit frame time.  Keep in mind that the
            // next frame has most likely already been scheduled by now so we play it
            // safe by ensuring the commit time is always at least one frame behind.
            if (callbackType == Choreographer.CALLBACK_COMMIT) {
                final long jitterNanos = now - frameTimeNanos;
                Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
                if (jitterNanos >= 2 * frameIntervalNanos) {
                    final long lastFrameOffset = jitterNanos % frameIntervalNanos
                            + frameIntervalNanos;
                    if (DEBUG_JANK) {
                        Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
                                + " ms which is more than twice the frame interval of "
                                + (frameIntervalNanos * 0.000001f) + " ms!  "
                                + "Setting frame time to " + (lastFrameOffset * 0.000001f)
                                + " ms in the past.");
                        mDebugPrintNextFrameTimeDelta = true;
                    }
                    frameTimeNanos = now - lastFrameOffset;
                    mLastFrameTimeNanos = frameTimeNanos;
                }
            }
        }
        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));
                }
                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);
        }
    }

CallbackRecord是一个链表,从上面可以看出其遍历了链表,并且执行了CallbackRecord里面的run方法。

6、CallbackQueue与CallbackRecord

private final CallbackQueue[] mCallbackQueues;

    private final class CallbackQueue {
        private CallbackRecord mHead;
		.
		.
		.
   }

mCallbackQueues是一个CallbackQueue的数组,里面保存着CallbackQueue对象,而CallbackQueue里面持有CallbackRecord对象。
CallbackQueue保存了通过postCallback()添加的任务,目前定义的任务类型有:

  • CALLBACK_INPUT:优先级最高,和输入事件处理有关。
  • CALLBACK_ANIMATION:优先级其次,和 Animation 的处理有关
  • CALLBACK_INSETS_ANIMATION:优先级其次,和 Insets Animation 的处理有关
  • CALLBACK_TRAVERSAL:优先级最低,和 UI 绘制任务有关
  • CALLBACK_COMMIT:最后执行,和提交任务有关(在 API Level 23 添加)
    并且这些任务都是按顺序添加进去的,当收到VSYNC信号时候,会按照顺序执行这些任务。

CallbackRecord的数据结构如下

    private static final class
    CallbackRecord {
        public CallbackRecord next;
        public long dueTime;
        public Object action; // Runnable or FrameCallback
        public Object token;

        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                // 通过postFrameCallback()或postFrameCallbackDelayed()添加的任务会执行这里
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
        }
    }

可以看出CallbackRecord其实是一个链表结构,它的run方法会调用传进来的Runnable的run方法或者FrameCallback的doFrame方法。

7、添加任务

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            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);
            }
        }
    }

里面会调用mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        @UnsupportedAppUsage
        public void addCallbackLocked(long dueTime, Object action, Object token) {
            CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
            CallbackRecord entry = mHead;
            if (entry == null) {
                mHead = callback;
                return;
            }
            if (dueTime < entry.dueTime) {
                callback.next = entry;
                mHead = callback;
                return;
            }
            while (entry.next != null) {
                if (dueTime < entry.next.dueTime) {
                    callback.next = entry.next;
                    break;
                }
                entry = entry.next;
            }
            entry.next = callback;
        }

其实就是取出数组里对应任务链表,按照时间添加到链表中的对应位置。

三、调用栈

1、Animation 回调调用栈

一般接触的多的是调用 View.postOnAnimation() 的时候,会使用到 CALLBACK_ANIMATION,View 的 postOnAnimation() 方法源码如下:

public void postOnAnimation(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    // 判断AttachInfo是否为空
    if (attachInfo != null) {
        // 如果不为null,直接调用Choreographer.postCallback()方法
        attachInfo.mViewRootImpl.mChoreographer.postCallback(
                Choreographer.CALLBACK_ANIMATION, action, null);
    } else {
        // 否则加入当前View的等待队列
        getRunQueue().post(action);
    }
}

另外 Choreographer 的 FrameCallback 也是用的 CALLBACK_ANIMATION,Choreographer 的 postFrameCallbackDelayed() 方法源码如下:

public void postFrameCallbackDelayed(Choreographer.FrameCallback callback, long delayMillis) { if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis);

2、ViewRootImpl 调用栈

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

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

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

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

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

四、总结

  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 接口,每当 VSYNC 到来时,其 doFrame() 函数将被调用。这个接口对 Android Animation 的实现起了很大的帮助作用。
  5. Choreographer 的主要功能是,当收到 VSYNC 信号时,去调用使用者通过 postCallback() 设置的回调函数。目前一共定义了五种类型的回调,它们分别是:
  6. CALLBACK_INPUT:处理输入事件处理有关
  7. CALLBACK_ANIMATION:处理 Animation 的处理有关
  8. CALLBACK_INSETS_ANIMATION:处理 Insets Animation 的相关回调
  9. CALLBACK_TRAVERSAL:处理和 UI 等控件绘制有关
  10. CALLBACK_COMMIT:处理 Commit 相关回调
  11. ListView 的 Item 初始化(obtain\setup) 会在 Input 里面也会在 Animation 里面,这取决于 CALLBACK_INPUT、CALLBACK_ANIMATION 会修改 View 的属性,所以要先与 CALLBACK_TRAVERSAL 执行。
  12. 每次执行完callback后会自动移除。

参考:https://henleylee.github.io/posts/2020/f831dca5.html,https://androidperformance.com/2019/10/22/Android-Choreographer/#/Choreographer-自身的掉帧计算逻辑

posted @ 2024-03-03 13:50  妖久  阅读(134)  评论(0编辑  收藏  举报