android进阶指南

Choreographer 学习

2020-05-31  本文已影响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帧起来。

上一篇下一篇

猜你喜欢

热点阅读