跳转到主内容
极星编程网:以代码为星,赴技术山海!

Android的VSYNC机制和UI刷新流程示例详解

目录前言

一、 Vsync信号详解

1、屏幕刷新相关知识点

2、VSYNC机制

二、UI刷新原理流程

1、VSYNC流程示意

2、view的invalidate

3、scheduleTraversals

4、申请VSYNC同步信号

6、FrameDisplayEventReceiver

7、doFrame

8、doTraversal

9、UI局部重绘

绘制总结前言屏幕刷新帧率不稳定,掉帧严重,无法保证每秒60帧,导致屏幕画面撕裂;

今天我们来讲解下VSYNC机制和UI刷新流程

一、 Vsync信号详解

1、屏幕刷新相关知识点

屏幕刷新频率: 一秒内屏幕刷新的次数(一秒内显示了多少帧的图像),单位 Hz(赫兹),如常见的 60 Hz。刷新频率取决于硬件的固定参数(不会变的);

逐行扫:显示器并不是一次性将画面显示到屏幕上,而是从左到右边,从上到下逐行扫描,顺序显示整屏的一个个像素点,不过这一过程快到人眼无法察觉到变化。以 60 Hz 刷新率的屏幕为例,这一过程即 1000 / 60 ≈ 16ms;

帧率: 表示 GPU 在一秒内绘制操作的帧数,单位 fps。例如在电影界采用 24 帧的速度足够使画面运行的非常流畅。而 Android 系统则采用更加流程的 60 fps,即每秒钟GPU最多绘制 60 帧画面。帧率是动态变化的,例如当画面静止时,GPU 是没有绘制操作的,屏幕刷新的还是buffer中的数据,即GPU最后操作的帧数据;

屏幕流畅度:即以每秒60帧(每帧16.6ms)的速度运行,也就是60fps,并且没有任何延迟或者掉帧;

FPS:每秒的帧数;

丢帧:在16.6ms完成工作却因各种原因没做完,占了后n个16.6ms的时间,相当于丢了n帧;

2、VSYNC机制

VSync机制: Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,VSync是Vertical Synchronization(垂直同步)的缩写,是一种在PC上很早就广泛使用的技术,可以简单的把它认为是一种定时中断。而在Android 4.1(JB)中已经开始引入VSync机制;

VSync机制下的绘制过程;CPU/GPU接收vsync信号,Vsync每16ms一次,那么在每次发出Vsync命令时,CPU都会进行刷新的操作。也就是在每个16ms的第一时间,CPU就会响应Vsync的命令,来进行数据刷新的动作。CPU和GPU的刷新时间,和Display的FPS是一致的。因为只有到发出Vsync命令的时候,CPU和GPU才会进行刷新或显示的动作。CPU/GPU接收vsync信号提前准备下一帧要显示的内容,所以能够及时准备好每一帧的数据,保证画面的流畅;

可见vsync信号没有提醒CPU/GPU工作的情况下,在第一个16ms之内,一切正常。然而在第二个16ms之内,几乎是在时间段的最后CPU才计算出了数据,交给了Graphics Driver,导致GPU也是在第二段的末尾时间才进行了绘制,整个动作延后到了第三段内。从而影响了下一个画面的绘制。这时会出现Jank(闪烁,可以理解为卡顿或者停顿)。这时候CPU和GPU可能被其他操作占用了,这就是卡顿出现的原因;

二、UI刷新原理流程

1、VSYNC流程示意

当我们通过setText改变TextView内容后,UI界面不会立刻改变,APP端会先向VSYNC服务请求,等到下一次VSYNC信号触发后,APP端的UI才真的开始刷新,基本流程如下:

setText最终调用invalidate申请重绘,最后会通过ViewParent递归到ViewRootImpl的invalidate,请求VSYNC,在请求VSYNC的时候,会添加一个同步栅栏,防止UI线程中同步消息执行,这样做为了加快VSYNC的响应速度,如果不设置,VSYNC到来的时候,正在执行一个同步消息;

2、view的invalidate

View会递归的调用父容器的invalidateChild,逐级回溯,最终走到ViewRootImpl的invalidate void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {// Propagate the damage rectangle to the parent view.

final AttachInfo ai = mAttachInfo;final ViewParent p = mParent;if (p != null && ai != null && l < r && t < b) {final Rect damage = ai.mTmpInvalRect;damage.set(l, t, r, b);p.invalidateChild(this, damage);}ViewRootImpl.java void invalidate() {mDirty.set(0, 0, mWidth, mHeight);if (!mWillDrawSoon) {scheduleTraversals();}}ViewRootImpl会调用scheduleTraversals准备重绘,但是,重绘一般不会立即执行,而是往Choreographer的Choreographer.CALLBACK_TRAVERSAL队列中添加了一个mTraversalRunnable,同时申请VSYNC,这个mTraversalRunnable要一直等到申请的VSYNC到来后才会被执行;

3、scheduleTraversals

ViewRootImpl.java// 将UI绘制的mTraversalRunnable加入到下次垂直同步信号到来的等待callback中去// mTraversalScheduled用来保证本次Traversals未执行前,不会要求遍历两边,浪费16ms内,不需要绘制两次void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;// 防止同步栅栏,同步栅栏的意思就是拦截同步消息mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();// postCallback的时候,顺便请求vnsc垂直同步信号scheduleVsyncLocked mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}

4、申请VSYNC同步信号

Choreographer知识点在上个文章详细介绍过;

Choreographer.java private void postCallbackDelayedInternal(int callbackType,Object action, Object token, long delayMillis) {synchronized (mLock) {final long now = SystemClock.uptimeMillis();final long dueTime = now + delayMillis;mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);if (dueTime <= now) {scheduleFrameLocked(now);}}}

5、scheduleFrameLocked

// mFrameScheduled保证16ms内,只会申请一次垂直同步信号// scheduleFrameLocked可以被调用多次,但是mFrameScheduled保证下一个vsync到来之前,不会有新的请求发出// 多余的scheduleFrameLocked调用被无效化private void scheduleFrameLocked(long now) {if (!mFrameScheduled) {mFrameScheduled = true;if (USE_VSYNC) {if (isRunningOnLooperThreadLocked()) {scheduleVsyncLocked();} else {// 因为invalid已经有了同步栅栏,所以必须mFrameScheduled,消息才能被UI线程执行Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);msg.setAsynchronous(true);mHandler.sendMessageAtFrontOfQueue(msg);}}}}在当前申请的VSYNC到来之前,不会再去请求新的VSYNC,因为16ms内申请两个VSYNC没意义;

再VSYNC到来之后,Choreographer利用Handler将FrameDisplayEventReceiver封装成一个异步Message,发送到UI线程的MessageQueue;

6、FrameDisplayEventReceiver

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {private boolean mHavePendingVsync;private long mTimestampNanos;private int mFrame;public FrameDisplayEventReceiver(Looper looper) {super(looper);}@Override public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {long now = System.nanoTime();if (timestampNanos > now) {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);}}之所以封装成异步Message,是因为前面添加了一个同步栅栏,同步消息不会被执行;

UI线程被唤起,取出该消息,最终调用doFrame进行UI刷新重绘;

7、doFrame

void doFrame(long frameTimeNanos, int frame) {final long startNanos;synchronized (mLock) {if (!mFrameScheduled) {return; // no work to do}long intendedFrameTimeNanos = frameTimeNanos;startNanos = System.nanoTime();final long jitterNanos = startNanos - frameTimeNanos;if (jitterNanos >= mFrameIntervalNanos) {final long skippedFrames = jitterNanos / mFrameIntervalNanos;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;}if (frameTimeNanos < mLastFrameTimeNanos) {scheduleVsyncLocked();return;}mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);mFrameScheduled = false;mLastFrameTimeNanos = frameTimeNanos;}try {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");mFrameInfo.markInputHandlingStart();doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);mFrameInfo.markAnimationsStart();doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);mFrameInfo.markPerformTraversalsStart();doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);performTraversals();}}doTraversal会先将栅栏移除,然后处理performTraversals,进行测量、布局、绘制,提交当前帧给SurfaceFlinger进行图层合成显示;

以上多个boolean变量保证了每16ms最多执行一次UI重绘;

9、UI局部重绘

View重绘刷新,并不会导致所有View都进行一次measure、layout、draw,只是这个待刷新View链路需要调整,剩余的View可能不需要浪费精力再来一遍;

View.java public RenderNode updateDisplayListIfDirty() {final RenderNode renderNode = mRenderNode;...

if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0|| !renderNode.isValid()|| (mRecreateDisplayList)) {} else {mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;mPrivateFlags &= ~PFLAG_DIRTY_MASK;}return renderNode;}绘制总结android最高60FPS,是VSYNC及决定的,每16ms最多一帧;

VSYNC要客户端主动申请,才会有;

有VSYNC到来才会刷新;

UI没更改,不会请求VSYNC也就不会刷新;

以上就是Android的VSYNC机制和UI刷新流程示例详解的详细内容,更多关于Android VSYNC机制UI刷新的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:Android MaterialAlertDialogBuilder修改按钮属性声网SDK教程Android UIKit 实时视频通话添加自定义背景Android性能优化系列篇UI优化使用Composing builds提升Android编译速度Android的UI调优教程Android UI动态设置带有Stroke渐变色背景Drawable

相关文章