Android Framework 学习:屏幕刷新机制
Posted 灰色飘零
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Framework 学习:屏幕刷新机制相关的知识,希望对你有一定的参考价值。
一、什么是屏幕刷新机制
屏幕的刷新包括三个步骤:
- CPU 计算屏幕数据
- GPU 进一步处理和缓存
- Display 将缓存中(buffer)的屏幕数据显示出来。
-
View 发起刷新的操作时,最终是走到了 ViewRootImpl 的 scheduleTraversals() 里去,然后这个方法会将遍历绘制 View 树的操作 performTraversals() 封装到 Runnable 里,传给 Choreographer,以当前的时间戳放进一个 mCallbackQueue 队列里,然后调用了 native 层的方法向底层注册监听下一个屏幕刷新信号事件。
-
当下一个屏幕刷新信号发出的时候,如果对这个事件进行监听,那么底层会回调 onVsync() 方法来通知。当 onVsync() 被回调时,会发一个 Message 到主线程,将后续的工作切到主线程来执行。切到主线程的工作就是去 mCallbackQueue 队列里根据时间戳将之前放进去的 Runnable 取出来执行,而这些 Runnable 就是遍历绘制 View 树的操作 performTraversals()。遍历操作完成后,就会去绘制那些需要刷新的 View。
-
当我们调用了 invalidate(),requestLayout(),等之类刷新界面的操作时,并不是马上就会执行这些刷新的操作,而是通过 ViewRootImpl 的 scheduleTraversals() 先向底层注册监听下一个屏幕刷新信号事件,然后等下一个屏幕刷新信号来的时候,才会去通过 performTraversals() 遍历绘制 View 树来执行这些刷新操作。
-
导致界面刷新丢帧的原因有两类:一是遍历绘制 View 树计算屏幕数据的时间超过了 16.6ms;二是,主线程一直在处理其他耗时的消息,导致遍历绘制 View 树的工作迟迟不能开始,从而超过了 16.6 ms 底层切换下一帧画面的时机。第一个原因是因为我们写的布局有问题,需要进行优化了。而第二个原因则是我们常说的避免在主线程中做耗时的任务。针对第二个原因,系统已经引入了同步屏障消息的机制,尽可能的保证遍历绘制 View 树的工作能够及时进行,但仍没办法完全避免,所以我们还是得尽可能避免主线程耗时工作。
二、Choreographer机制
Choreographer机制,用于同Vsync机制配合,统一动画、输入和绘制的时机。
本节讲解Choreographer机制主要是从绘制方面做阐述,界面的绘制要从ViewRootImpl的requestLayout开始说起,其源码如下:
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); // 检查是否在UI线程 mLayoutRequested = true; // 是否进行measure和layout布局 scheduleTraversals(); } } void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; // 拦截同步消息 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 执行绘制操作 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
这里需要注意的地方有两点:
- postSyncBarrier()方法:Handler 的同步屏障,作用是可以拦截 Looper 对同步消息的获取和分发,加入同步屏障之后Looper 只会获取和处理异步消息,如果没有异步消息那么就会进入阻塞状态。通过同步屏障,就为UI绘制渲染处理操作的优先处理提供了基础。
- Choreographer:编舞者,统一动画、输入和绘制时机。
1. Choreographer 启动逻辑
Choreographer的创建是在ViewRootImpl的构造函数中。
public ViewRootImpl(Context context, Display display) { mContext = context; mWindowSession = WindowManagerGlobal.getWindowSession(); ... mChoreographer = Choreographer.getInstance(); ... }
下面是Choreographer的getInstance执行的代码:
/** * Gets the choreographer for the calling thread. Must be called from * a thread that already has a {@link android.os.Looper} associated with it. * * @return The choreographer for this thread. * @throws IllegalStateException if the thread does not have a looper. */ public static Choreographer getInstance() { return sThreadInstance.get(); } // Thread local storage for the 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; } }; private static volatile Choreographer mMainInstance;
可以看到每一个Looper线程都有自己的Choreographer,其他线程发送的回调只能运行在对应Choreographer所属的Looper线程上。
这里我们再看一下Choreographer的构造函数:
private Choreographer(Looper looper, int vsyncSource) { mLooper = looper; mHandler = new FrameHandler(looper); mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null; mLastFrameTimeNanos = Long.MIN_VALUE; 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)); }
Choreographer类中有一个Looper和一个FrameHandler变量。变量USE_VSYNC用于表示系统是否是用了Vsync同步机制,该值是通过读取系统属性debug.choreographer.vsync来获取的。如果系统使用了Vsync同步机制,则创建一个FrameDisplayEventReceiver对象用于请求并接收Vsync事件,最后Choreographer创建了一个大小为3的CallbackQueue队列数组,用于保存不同类型的Callback。
不同类型的Callback包括如下4种:
CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT。
CallbackQueue是一个容量为4的数组,每一个元素作为头指针,引出对应类型的链表,4种事件就是通过这4个链表来维护的。而FrameHandler中主要处理三类消息:
private final class FrameHandler extends Handler { public FrameHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_DO_FRAME: doFrame(System.nanoTime(), 0); break; case MSG_DO_SCHEDULE_VSYNC: doScheduleVsync(); // 请求VSYNC信号 break; case MSG_DO_SCHEDULE_CALLBACK: doScheduleCallback(msg.arg1); break; } } }
2. Choreographer执行流程
执行流程就要需要从下面的代码开始说起:
// 执行绘制操作 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
最终会调到 postCallbackDelayedInternal 方法:
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { synchronized (mLock) { // 当前时间 final long now = SystemClock.uptimeMillis(); // 回调执行时间,为当前时间加上延迟的时间 final long dueTime = now + delayMillis; // obtainCallbackLocked会将传入的3个参数转换为CallbackRecord,然后CallbackQueue根据回调类型将CallbackRecord添加到链表上。 mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime >= now) { // 如果delayMillis=0的话,dueTime=now,则会马上执行 scheduleFrameLocked(now); } else { // 如果dueTime > now,则发送一个what为MSG_DO_SCHEDULE_CALLBACK类型的定时消息,等时间到了再处理,其最终处理也是执行scheduleFrameLocked(long now)方法 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } }
mCallbackQueues先把对应的callback添加到链表上来,然后判断是否有延迟,如果没有则会马上执行scheduleFrameLocked,如果有,则发送一个what为MSG_DO_SCHEDULE_CALLBACK类型的定时消息,等时间到了再处理,其最终处理也是执行scheduleFrameLocked(long now)方法。
private void scheduleFrameLocked(long now) { if (!mFrameScheduled) { mFrameScheduled = true; if (USE_VSYNC) { // 如果使用了VSYNC,由系统值确定 if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame on vsync."); } if (isRunningOnLooperThreadLocked()) { // 请求VSYNC信号,最终会调到Native层,Native处理完成后触发FrameDisplayEventReceiver的onVsync回调,回调中最后也会调用doFrame(long frameTimeNanos, int frame)方法 scheduleVsyncLocked(); } else { // 在UI线程上直接发送一个what=MSG_DO_SCHEDULE_VSYNC的消息,最终也会调到scheduleVsyncLocked()去请求VSYNC信号 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); } } else { // 没有使用VSYNC final long nextFrameTime = Math.max( mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now); if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); } // 直接发送一个what=MSG_DO_FRAME的消息,消息处理时调用doFrame(long frameTimeNanos, int frame)方法 Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); } } }
判断USE_VSYNC,如果使用了VSYNC,走scheduleVsyncLocked,即请求VSYNC信号,最终调用doFrame;如果没使用VSYNC,则通过异步Message执行doFrame。
下面我们看一下doFrame的代码:
void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { if (!mFrameScheduled) { return; // mFrameScheduled=false,则直接返回。 } long intendedFrameTimeNanos = frameTimeNanos; //原本计划的绘帧时间点 startNanos = System.nanoTime();//保存起始时间 //由于Vsync事件处理采用的是异步方式,因此这里计算消息发送与函数调用开始之间所花费的时间 final long jitterNanos = startNanos - frameTimeNanos; //如果线程处理该消息的时间超过了屏幕刷新周期 if (jitterNanos >= mFrameIntervalNanos) { //计算函数调用期间所错过的帧数 final long skippedFrames = jitterNanos / mFrameIntervalNanos; //当掉帧个数超过30,则输出相应log if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); } final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; frameTimeNanos = startNanos - lastFrameOffset; //对齐帧的时间间隔 } //如果frameTimeNanos小于一个屏幕刷新周期,则重新请求VSync信号 if (frameTimeNanos < mLastFrameTimeNanos) { scheduleVsyncLocked(); return; } mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; } try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame"); //分别回调CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL事件 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 { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
doCallBacks里的run方法执行了,也就是真正执行了View的绘制流程了。
3.Choreographer总结
1). Choreographer支持4种类型事件:输入、绘制、动画、提交,并通过postCallback在对应需要同步vsync进行刷新处进行注册,等待回调。
2). Choreographer监听底层Vsync信号,一旦接收到回调信号,则通过doFrame统一对java层4种类型事件进行回调。
三、屏幕绘制过程的流程图
参考资料:
以上是关于Android Framework 学习:屏幕刷新机制的主要内容,如果未能解决你的问题,请参考以下文章
Framework源码面试六部曲:6.Android屏幕刷新机制
系统方向学习总结1--Android 10.0 Settings 显示菜单增加选择屏幕密度选项
系统方向学习总结1--Android 10.0 Settings 显示菜单增加选择屏幕密度选项