Choreographer

简介

Choreographer 经过内部的 FrameDisplayEventReceiver 来接收 Vsync(是由 SurfaceFlinger 下发的) 信号,接收到 Vsync 后统一处理 InputEvent (部分事情由这儿处理,下发至 ViewRootImpl)、AnimationTraversal (也便是对应的 View 的 layout, measure 和 draw 流程,事情是下发至 ViewRootImpl,然后经过冒泡的办法顺次下发至一切的 View)等使命。
这儿以 View 的制作为例子来描述一下这个过程:假如 View 的 UI 需求修正,咱们都会调用 View#invalidate() 办法恳求从头制作,这个办法会通常调用 parent 的 invalidateChild 办法,终究会调用到 ViewRootImpl 中的 invalidateChild 办法,在 ViewRootImpl 中会直接向 Choreographer 中恳求添加一个 Traversal 类型的使命,在下次的 Vsync 信号来了后就会履行这个使命(假如这时还有其他使命也会履行),终究完结制作。
Activity 进行 resume 的生命周期时会创立 ViewRootImpl 实例 (其他和 UI 相关的当地也有或许创立,例如 Dialog.),ViewRootImpl 的结构函数中会去获取 Choreographer 实例,这是一个单例对象。

后续的代码阅览都是依据 API 31

获取 Vsync

Choreographer 的结构函数中会初始化 FrameDisplayEventReceiver 实例,来用于获取 Vsync 信号,其中 onVsync 办法便是笔直同步的信号回调,其中该办法中包含了帧的一些重要信息,包括 Vsync 信号的时刻戳、帧序号、帧距离、帧制作完结的 Deadline:


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

onVsync 回调办法中首要查看信号的时刻戳假如大于当时的时刻丢掉当时信号,假如上次的 Vsync 信号还没有处理完也直接丢掉。然后经过 handler (在初始化的时分创立,作业在主线程) 将其加入到使命行列中,留意这儿的 msg 是异步的(处理优先级更高),终究使命的处理是在 run 办法中,处理的入口函数是 doFrame 办法,这儿还有一点要提醒一下 onVsync 信号回调不是每次都会调用的,需求手动调用 FrameDisplayEventReceiver#scheduleVsync() 办法后才会把下次的 Vsync 信号回调给咱们。

履行使命

在讲履行使命之前,咱们先讲一讲这些等候履行的使命是放在哪里的,怎样放的。他们都是按照分类放在 private final CallbackQueue[] mCallbackQueues; 中,这个数组中顺次放了 InputEvent(CALLBACK_INPUT), Animation(CALLBACK_ANIMATIONCALLBACK_INSETS_ANIMATION), Traversal(CALLBACK_TRAVERSAL) 的使命行列。CallbackQueue 中的实现是链表,履行完的使命就会被移除链表,他们的详细代码我就不贴了,感兴趣的自己去看一下。

接下来看看上面说到的使命处理的入口函数 doFrame

在开始使命前,会经过当时的时刻与 Vsync 产生的时刻作差然后和帧距离相比,假如大于帧距离就表明前面帧处理的时刻过长,导致了掉帧:


    void doFrame(long frameTimeNanos, int frame,
                 DisplayEventReceiver.VsyncEventData vsyncEventData) {
        // ...
        long intendedFrameTimeNanos = frameTimeNanos;
        startNanos = System.nanoTime();
        final long jitterNanos = startNanos - frameTimeNanos;
        if (jitterNanos >= frameIntervalNanos) {
            final long skippedFrames = jitterNanos / frameIntervalNanos;
            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;
        }
        //...
    }

上面的代码块计算了掉帧,依据掉帧的 offset 从头计算了帧时刻。

下面的代码添加了两个越过 frame 制作的逻辑,越过本次制作后会经过 scheduleVsyncLocked() 办法在再次恳求下次的 Vsync 信号。


    void doFrame(long frameTimeNanos, int frame,
                 DisplayEventReceiver.VsyncEventData vsyncEventData) {
        // ...
        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;
            }
        }
        //...
    }

假如当时的帧时刻戳逼上次的帧时刻戳还小的时分越过本次制作(通常是前面的帧制作超时导致的);还有便是经过 mFPSDivisor 来约束改写时刻距离,这样可以操控帧率,通常这个值是 1,也便是不约束。

以下就进入使命履行了:


    void doFrame(long frameTimeNanos, int frame,
                 DisplayEventReceiver.VsyncEventData vsyncEventData) {
        // ...
        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
                vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
        mFrameInfo.markInputHandlingStart();
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
                frameIntervalNanos);
        mFrameInfo.markPerformTraversalsStart();
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
        //...
    }

其中 mFrameInfo 中记录了许多有用的信息,包括 Vsync 的开始时刻、帧距离、制作完结的deadline、每个阶段的耗时等信息,在应用功能分析时这些数据很有用。
使命按 CALLBACK_INPUT, CALLBACK_ANIMATION, CALLBACK_INSETS_ANIMATION, CALLBACK_TRAVERSAL 顺次履行,终究的实践使命履行都调用了 doCallbacks 办法:


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

首要经过需求履行的类型从 mCallbackQueues 中拿到想要的使命行列,然后依据时刻把过期的 callback 过滤掉,假如是 CALLBACK_COMMITVsync 使命的终究一种类型) 还会记录一些超时的日志和更新一些状况相关的变量。然后顺次履行使命,Callback 对应的类是 CallbackRecord,履行时调用对应的 run 办法。终究收回这些 Callback。


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) {
            ((FrameCallback)action).doFrame(frameTimeNanos);
        } else {
            ((Runnable)action).run();
        }
    }
}

CallbackRecord 中的代码真的非常简单,就不多说了.

再看看 View 的恳求制作和动画

View 的恳求制作

众所周知假如需求更新 View 的 UI,需求调用 View#invalidate() 办法。终究几经跳转会履行下面的代码:

// ...
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
    final Rect damage = ai.mTmpInvalRect;
    damage.set(l, t, r, b);
    p.invalidateChild(this, damage);
}
// ...

他会调用 parent 的 invalidateChild 办法,咱们继续跟进 ViewGroupinvalidateChild 办法,发现它也会调用它的 parent 的 invalidateChild 办法,也便是说它会一层一层往上调用,直到顶层的 parent,这儿我直接说定论,顶层的 parent 便是 ViewRootImpl
ViewRootImpl#invalidateChild 办法中终究会兜兜转转调用到 scheduleTraversal 办法中。

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

经过层层调用,终究总算看到了咱们了解的 Choreographer,他在主线程的 Looper 行列中加了一个屏障(进步使命的优先级), 然后将 mTraversalRunnable 添加到 CALLBACK_TRAVERSAL 行列中,等候下次 Vsync 信号来的时分履行。

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

TraversalRunnable 直接在 run 中调用 doTraversal 办法,这个办法便是触发整个 ViewTree measure、layout 和 draw 的办法。

Choreographer#postCallback 办法中终究会调用到 postCallbackDelayedInternal 办法中:


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

首要将使命添加到对应 callbackType 的行列中,假如恳求的时刻小于等于当时时刻,直接恳求下次的 Vsync 信号;假如还未到履行的时刻,那就经过 handler 发一个推迟使命,时刻到了再恳求 Vsync 信号.

View 动画

咱们直接看 View#postOnAnimation 办法:


public void postOnAnimation(Runnable action) {
   final AttachInfo attachInfo = mAttachInfo;
   if (attachInfo != null) {
       attachInfo.mViewRootImpl.mChoreographer.postCallback(
               Choreographer.CALLBACK_ANIMATION, action, null);
   } else {
       // Postpone the runnable until we know
       // on which thread it needs to run.
       getRunQueue().post(action);
   }
}

一点弯子都不绕,直接向 Choreographer 中直接添加 CALLBACK_ANIMATION 使命.