美文网首页
Android 图形系统(6)---- Choreographe

Android 图形系统(6)---- Choreographe

作者: 特立独行的佩奇 | 来源:发表于2023-04-03 21:36 被阅读0次

Choreographer 概述

Android 4.1 之后增加了Choreographer 机制,用于同Vsync配合,同步处理输入,动画,绘制三个UI操作,实际上UI显示每一帧的时候要完成的操作只有这几样,下面是官网的说明:

  • Choreographer接收显示系统的垂直同步信号(Vsync),在下一个Frame 渲染的时候执行这些操作
  • Choreographer 中文的含义是编舞者,可以理解为知道UI操作一起顺序协调工作

通过Choreographer#postFrameCallback设置callback,再下一个Frame 被渲染的实际到来时,会执行这个callback,是
Choreographer 中的Looper 对象执行的
Callback 有四种类型 input,animation,traversal,commit 这四种操作都是 Choreographer 通过Vsync 触发的

Choreographer 解析

Choreographer 类图:


choreographer_classDiagram.jpg
View 更新流程
Choreographer 实例化

Choreographer 是单例模式,ViewRootImpl 通过 Choreographer.getInstance() 函数获取 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;
        }
    };

通过上面的ThreadLocal,保证每一个线程对应一个Choreographer,存储的方式是以ThreadLocal作为Key,Choreographer作为value存储到ThreadLocalMap中

Choreographer 申请Vsync 流程

Choreographer 的申请Vsync流程图:


choreographer_mainflow01.jpg choreographer_mainflow02.jpg

在ViewRootImpl 中调用了 postCallback 方法后,可以看到通过 addCallBackLocked方法,添加了一条CallbackRecord 数据,其中的action 就是对应之前的ViewRootIml中的mTraversalRunnable
然后判断时间是否在当前时间之后,也就是有没有延迟,如果有延迟就发送延迟消息MSG_DO_SCHEDULE_CALLBACK 到 Handler 所在的线程,Handler 的处理函数在 FrameHandler 类中,会执行
doScheduleCallback函数中的scheduleFrameLocked 方法;

scheduleFrameLocked 用于申请Vsync信号,在该函数中首先判断是否开启了Vsync;

  1. 如果没有开启Vsync,直接调用 doFrame方法;
  2. 如果开启了Vsync,判断在不在主线程中,如果在主线程中就运行scheduleVsyncLocked 函数;如果不在就切换线程,也会调用到scheduleVsyncLocked 方法,这个方法就是实际上用于申请 Vsync 的方法了

另外需要注意的是,使用choreographer中的handler 发送消息的时候,都调用了msg.setAsynchronous(true);方法;这个方法就是设置消息为异步消息,因为我们一开始的时候设置了同步屏障,所以异步消息就会先执行,这里设置为异步也就是为了让消息第一时间执行而不是受到其他Handler的影响

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

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  • scheduleVsyncLocked 执行流程
    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

结合上面的类图,原理就是通过FrameDisplayEventReceiver 类型的mDisplayEventReceiver,请求native 层面的垂直同步信号Vsync

mDisplayEventReceiver 是 choreographer类中的成员,FrameDisplayEventReceiver 继承自 DisplayEventReceiver;设计就是为了处理Vsync 信号的申请和接收

使用 DisplayEventReceiver 的函数nativeScheduleVsync申请Vsync 信号,然后接收到Vsync信号的时候就会回调onVsync方法了

        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
            // Ignore vsync from secondary display.
            // This can be problematic because the call to scheduleVsync() is a one-shot.
            // We need to ensure that we will still receive the vsync from the primary
            // display which is the one we really care about.  Ideally we should schedule
            // vsync for a particular display.
            // At this time Surface Flinger won't send us vsyncs for secondary displays
            // but that could change in the future so let's log a message to help us remember
            // that we need to fix this.
            if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
                Log.d(TAG, "Received vsync from secondary display, but we don't support "
                        + "this case yet.  Choreographer needs a way to explicitly request "
                        + "vsync for a specific display to ensure it doesn't lose track "
                        + "of its scheduled vsync.");
                scheduleVsync();
                return;
            }

            // 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;
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

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

在onVsync 函数中,同样通过Handler 发送了一条消息,执行了本身的 Runnable 回调方法,也就是doFrame

Choreographer doFrame流程
  1. 设置当前帧的开始绘制时间,上面说过开始绘制要在vsync信号来的时候开始,保证两者时间对应;所以如果时间没对上,就是发送了跳帧,那么就要修正这个时间,保证后续的时间对应上
  2. 执行所有的Callback任务
Choreographer doCallback流程
 void doFrame(long frameTimeNanos, int frame) {
......
try {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
    AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

    mFrameInfo.markInputHandlingStart();
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

    mFrameInfo.markAnimationsStart();
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

    mFrameInfo.markPerformTraversalsStart();
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
    AnimationUtils.unlockAnimationClock();
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

按照类型,从mCallbackQueues队列中取任务,并执行对应的CallbackRecord的run方法
在这个run方法中,判断了token,并且执行了action 对应的方法;
回顾一下 ViewrootImpl 中传入的方法

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

传入的方法就是ViewRootImpl 内定义的mTraversalRunnable 对象,所以最终又回到了ViewRootImpl 本身,通过Choreographer申请了Vsync信号,然后Choreographer接收了Vsync信号,在Traversal的流程中调用了本身的doTraversal函数

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

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

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

doTraversal 调用了 performTraversals方法,也就开始了测量,布局,绘制的步骤,同时关闭了同步屏障

相关文章

网友评论

      本文标题:Android 图形系统(6)---- Choreographe

      本文链接:https://www.haomeiwen.com/subject/cehjddtx.html