让建站和SEO变得简单

让不懂建站的用户快速建站,让会建站的提高建站效率!

Vsync 信号机制和 UI 刷新历程

发布日期:2022-03-13 19:36    点击次数:202

 

序言

屏幕刷新帧率不领路,掉帧严重,无法保证每秒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

View.java  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);          <!--添加一个措置触摸事件的回调,提神中间有Touch事件过来-->         if (!mUnbufferedInputDispatch) {             scheduleConsumeBatchedInput();         }         notifyRendererOfFramePending();         pokeDrawLockIfNeeded();     } } 
4、肯求VSYNC同步信号
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) {         <!--肯求VSYNC同步信号-->             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,一般是上传vsync的机制出了问题-->                 timestampNanos = now;             }             <!--若是上一个vsync同步信号没推行,那就不应该相应下一个(可能是其他线程通过某种表情请求的)-->               if (mHavePendingVsync) {                 Log.w(TAG, "Already have a pending vsync event.  There should only be "                         + "one at a time.");             } else {                 mHavePendingVsync = true;             }             <!--timestampNanos其实是本次vsync产生的时刻,从办事端发过来-->             mTimestampNanos = timestampNanos;             mFrame = frame;             Message msg = Message.obtain(mHandler, this);             <!--由于照旧存在同步栅栏,是以VSYNC到来的Message需要行为异步音信发送畴昔-->             msg.setAsynchronous(true);             mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);         }         @Override         public void run() {             mHavePendingVsync = false;             <!--这里的mTimestampNanos其实即是本次Vynsc同步信号到来的时候,然则推行这个音信的时候,可能延伸了-->             doFrame(mTimestampNanos, mFrame);         }     } 
之是以封装成异步Message,是因为前边添加了一个同步栅栏,同步音信不会被推行; UI线程被唤起,取出该音信,最终调用doFrame进行UI刷新重绘; 7、doFrame
void doFrame(long frameTimeNanos, int frame) {     final long startNanos;     synchronized (mLock) {     <!--做了好多东西,都是为了保证一次16ms有一次垂直同步信号,有一次input 、刷新、重绘-->         if (!mFrameScheduled) {             return; // no work to do         }        long intendedFrameTimeNanos = frameTimeNanos;         startNanos = System.nanoTime();         final long jitterNanos = startNanos - frameTimeNanos;         <!--查抄是否因为延伸推行掉帧,每大于16ms,就多掉一帧-->         if (jitterNanos >= mFrameIntervalNanos) {             final long skippedFrames = jitterNanos / mFrameIntervalNanos;             <!--跳帧,其实即是上一次请求刷新被延伸的时刻,然则这里skippedFrames为0不代表莫得掉帧-->             if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {             <!--skippedFrames很大一定掉帧,然则为 0,去并非没掉帧-->                 Log.i(TAG, "Skipped " + skippedFrames + " frames!  "                         + "The application may be doing too much work on its main thread.");             }             final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;                 <!--运行doFrame的实在有用时刻戳-->             frameTimeNanos = startNanos - lastFrameOffset;         }         if (frameTimeNanos < mLastFrameTimeNanos) {             <!--这种情况一般是生成vsync的机制出现了问题,那就再肯求一次-->             scheduleVsyncLocked();             return;         }           <!--intendedFrameTimeNanos是蓝本要绘图的时刻戳,frameTimeNanos是实在的,不错在渲染用具中标记延伸VSYNC若干-->         mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);         <!--移除mFrameScheduled判断,确认措置运行了,-->         mFrameScheduled = false;         <!--更新mLastFrameTimeNanos-->         mLastFrameTimeNanos = frameTimeNanos;     }     try {          <!--实在运行措置业务-->         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");         <!--措置打包的move事件-->         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);     } } 
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