Choreographer 学习

作者: 过期的薯条 | 来源:发表于2020-05-31 21:54 被阅读0次

    1.引言

    看到别人在项目中使用这个类,来得到每帧数据,觉得有点新鲜。仔细研究发现Choreographer 还很有点门道。涉及到了android ui展示的一些知识,了解了一些相关的知识点和概念。下面一一介绍下

    2.正题

    2.1 知识概念

    什么是屏幕刷新率 ?

    首先我们需要知道什么是屏幕刷新率,简单来说,屏幕刷新率是一个硬件的概念,是说屏幕这个硬件刷新画面的频率。
    举例来说,60Hz 刷新率意思是:这个屏幕在 1 秒内,会刷新显示内容 60 次;那么对应的,90Hz 是说在 1 秒内刷新显示内容 90 次。至于显示的内容是什么,屏幕这边是不关心的,他只是从规定的地方取需要显示的内容,然后显示到屏幕上。

    什么是 FPS ?

    首先需要说明的是 FPS 是一个软件的概念,与屏幕刷新率这个硬件概念要区分开,FPS 是由软件系统决定的。
    FPS 是 Frame per Second 的缩写,意思是每秒产生画面的个数。举例来说,60FPS 指的是每秒产生 60 个画面;90FPS 指的是每秒产生 90 个画面。

    什么是 Vsync ?

    VSync 是垂直同期( Vertical Synchronization )的简称。屏幕的刷新过程是每一行从左到右(行刷新,水平刷新,Horizontal Scanning),从上到下(屏幕刷新,垂直刷新,Vertical Scanning)。
    对比 60 fps :
    60 fps 的系统 , 1s 内需要生成 60 个可供显示的 Frame , 也就是说绘制一帧需要 16.67ms ( 1/60 ) , 才会不掉帧 ( FrameMiss ).
    90 fps 的系统 , 1s 内生成 90 个可供显示的 Frame , 也就是说绘制一帧需要 11.11ms ( 1/90 ) , 才不会掉帧 ( FrameMiss ).

    什么是 Input 扫描周期 ?

    Input 的扫描周期在 8 ms左右, 不同的手机会有不同, 由于 Android 系统的 Display 系统是以 Vsync 为基础的, Input 事件也是在 Vsync 到来的时候才会去处理.
    所以当一个 Vsync 周期为 16.67ms , Input 周期为 8ms 的时候, 可以保证一个 Vsync 周期内存在 2 个 Input 点.
    当一个 Vsync 周期为 11.11ms , Input 周期为 8ms 的时候, 一个 Vsync 周期内可能存在 2 个 Input 点. 也可能存在 1个 Input 点. 这会带来不均匀的 Input 体验.
    现在市场上有很多90HZ 和120HZ刷新频率的手机。典型代表是一加的7T 和一加8pro。120HZ指的是 屏幕每秒钟能展示120张图片。相对于传统的60HZ手机。120张图片意味着 动画更加细腻。视觉上更加连贯,一般>=30帧/s.人眼是无法分辨的。当在快速滑动的 时候,就好发现不连贯,卡顿现象。120HZ此时此刻的有点就突显出来了。

    Android的屏幕刷新机制

    渲染操依赖于三个核心组件:CPU与GPU与Display。CPU负责包括Measure,Layout,Record,Execute的计算操作,GPU负责Rasterization(栅格化)操作,将button信息转化成像素信息保存到buffeer中。Display定期就会去取像素信息,然后刷新图片。

    60Hz的手机,得1000/60=16.6ms 要绘制一帧。90HZ大概是11ms 刷新一次; 120Hz大概是8.3ms 刷新一次。刷新频率越高,时间压缩的越短。对硬件的要求就越高。

    2.2 Choreographer 流程分析

    Choreographer 系统独一无二的类,是一个单例。通过Choreographer.getInstance()获取。postCallback很重要

     /**
         * Posts a callback to run on the next frame.
         * <p>
         * The callback runs once then is automatically removed.
         * </p>
         *
         * @param callbackType The callback type.
         * @param action The callback action to run during the next frame.
         * @param token The callback token, or null if none.
         *
         * @see #removeCallbacks
         * @hide
         */
        @TestApi
        public void postCallback(int callbackType, Runnable action, Object token) {
            postCallbackDelayed(callbackType, action, token, 0);
        }
    

    callbackType 参数对应不同阶段的调用:

    public static final int CALLBACK_INPUT = 0; //输入类型的callback
    public static final int CALLBACK_ANIMATION = 1; //动画callback
    public static final int CALLBACK_INSETS_ANIMATION = 2;
    public static final int CALLBACK_TRAVERSAL = 3;  开始测量callback
    public static final int CALLBACK_COMMIT = 4;//测量完毕callback
    

    ViewRootImpl.scheduleTraversals
       void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//下一帧开始测量
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    

    其中,mChoreographer.postCallback(
    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 方法中的mTraversalRunnable 回调将会 调用:performTraversals方法 开启下一轮的 measure,layout,draw


    Choreographer 构造方法:

     private Choreographer(Looper looper, int vsyncSource) {
            mLooper = looper;
            mHandler = new FrameHandler(looper);
    
           //可以更改系统属性来决定是否用Vsync机制 
            mDisplayEventReceiver = USE_VSYNC
                    ? new FrameDisplayEventReceiver(looper, vsyncSource)
                    : null;
            mLastFrameTimeNanos = Long.MIN_VALUE;
    
            //getRefreshRate() 获取刷新的屏幕预测下:60HZ  90HZ  120HZ
            mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
    
            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));
        }
    

    USE_VSYNC:通过获取系统属性得到的。当USE_VSYNC==true,那么在60HZ上 延时时间就是16.7ms。当USE_VSYNC==false,那么延时时间默认为10ms

    1000000000 也就是我们说的1s, getRefreshRate() 刷新速度。60Hz手机上 这个值就是60开头, 90HZ 上这个就是90开头。

    其中FrameDisplayEventReceiver 类是用来接收Vsync信号的。

    接收到脉冲信号之后 就会执行doFram方法:


    image.png

    doFrame()方法中调用了scheduleVsync()。用来请求下一次脉冲信号。

    doFrame() 调用了:

     AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
    
                mFrameInfo.markInputHandlingStart();
                doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);// 各种keyEvent时间 
    
                mFrameInfo.markAnimationsStart();
                doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);//动画
                doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
    
                mFrameInfo.markPerformTraversalsStart();
                doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);//从新测量view
    
                doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);//
    

    其中:
    Choreographer.CALLBACK_INPUT 对应着 ViewRootImpl.ConsumeBatchedInputRunnable 方法

    Choreographer.CALLBACK_ANIMATION对应着 ViewRootImpl.InvalidateOnAnimationRunnable方法 让view 重新绘制达到动画的目的

    Choreographer.CALLBACK_TRAVERSAL 对应着 ViewRootImpl.TraversalRunnable 对view树进行测量,布局,绘制

    时序图如下:

    image.png

    我之前有问题:在进行动画的时候。我们设置了时间。系统如何保证这个动画分割的每一帧图片,时间间隔是多少。

    学习了本章 我知道了,原来每隔16ms 系统就会自动去请求一帧数据。假如一个动画时间设置成160ms。那么就相当于 这个动画大约有10帧起来。

    相关文章

      网友评论

        本文标题:Choreographer 学习

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