美文网首页
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