Choreographer 学习
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帧起来。