春游——科学馆

时间:2017-12-25 19:47:58来源:杰瑞文章网点击:作文字数:400字
从UI控件内容更改到被重新绘制到屏幕上,这中间到底经历了什么?另外,连续两次setTextView到底会触发几次UI重绘呢?为什么Android APP的帧率最高是60FPS呢,这就是本文要讨论的内容。 以电影为例,动画至少要达到24FPS,才能保证画面的流畅性,低于这个值,肉眼会感觉到卡顿。在手机上,这个值被调整到60FPS,增加丝滑度,这也是为什么有个(1000/60)16ms的指标,一般而言目前的Android系统最高FPS也就是60,它是通过了一个VSYNC来保证每16ms最多绘制一帧。简而言之:UI必须至少等待16ms的间隔才会绘制下一帧,所以连续两次setTextView只会触发一次重绘。下面来具体看一下UI的重绘流程。 UI刷新流程示意 以Textview为例 ,当我们通过setText改变TextView内容后,UI界面不会立刻改变,APP端会先向VSYNC服务请求,等到下一次VSYNC信号触发后,APP端的UI才真的开始刷新,基本流程如下 image.png 从我们的代码端来看如下:setText最终调用invalidate申请重绘,最后会通过ViewParent递归到ViewRootImpl的invalidate,请求VSYNC,在请求VSYNC的时候,会添加一个同步栅栏,防止UI线程中同步消息执行,这样做为了加快VSYNC的响应速度,如果不设置,VSYNC到来的时候,正在执行一个同步消息,那么UI更新的Task就会被延迟执行,这是Android的Looper跟MessageQueue决定的。 APP端触发重绘,申请VSYNC流程示意 image.png 等到VSYNC到来后,会移除同步栅栏,并率先开始执行当前帧的处理,调用逻辑如下 VSYNC回来流程示意 image.png doFrame执行UI绘制的示意图 image.png UI刷新源码跟踪 同TextView类似,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到来后才会被执行,如下: 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(); } } 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); } } } scheduleTraversals利用mTraversalScheduled保证,在当前的mTraversalRunnable未被执行前,scheduleTraversals不会再被有效调用,也就是Choreographer.CALLBACK_TRAVERSAL理论上应该只有一个mTraversalRunnable的Task。mChoreographer.postCallback将mTraversalRunnable插入到CallBack之后,会接着调用scheduleFrameLocked请求Vsync同步信号 // 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); } } } } scheduleFrameLocked跟上一个scheduleTraversals类似,也采用了利用mFrameScheduled来保证:在当前申请的VSYNC到来之前,不会再去请求新的VSYNC,因为16ms内申请两个VSYNC没意义。再VSYNC到来之后,Choreographer利用Handler将FrameDisplayEventReceiver封装成一个异步Message,发送到UI线程的MessageQueue, 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刷新重绘 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); doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } doFrame也采用了一个boolean遍历mFrameScheduled保证每次VSYNC中,只执行一次,可以看到,为了保证16ms只执行一次重绘,加了好多次层保障。doFrame里除了UI重绘,其实还处理了很多其他的事,比如检测VSYNC被延迟多久执行,掉了多少帧,处理Touch事件(一般是MOVE),处理动画,以及UI,当doFrame在处理Choreographer.CALLBACK_TRAVERSAL的回调时(mTraversalRunnable),才是真正的开始处理View重绘: final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } 回到ViewRootImpl调用doTraversal进行View树遍历, // 这里是真正执行了, void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); performTraversals(); } } doTraversal会先将栅栏移除,然后处理performTraversals,进行测量、布局、绘制,提交当前帧给SurfaceFlinger进行图层合成显示。以上多个boolean变量保证了每16ms最多执行一次UI重绘,这也是目前Android存在60FPS上限的原因。 注: VSYNC同步信号需要用户主动去请求才会收到,并且是单次有效。 UI局部重绘 某一个View重绘刷新,并不会导致所有View都进行一次measure、layout、draw,只是这个待刷新View链路需要调整,剩余的View可能不需要浪费精力再来一遍,反应再APP侧就是:不需要再次调用所有ViewupdateDisplayListIfDirty构建RenderNode渲染Op树,如下 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也就不会刷新 UI局部重绘其实只是省去了再次构建硬件加速用的DrawOp树(复用上衣帧的) 作者:看书的小蜗牛 Android VSYNC (Choreographer)与UI刷新原理分析.md 仅供参考,欢迎指正
作文投稿

春游——科学馆一文由杰瑞文章网免费提供,本站为公益性作文网站,此作文为网上收集或网友提供,版权归原作者所有,如果侵犯了您的权益,请及时与我们联系,我们会立即删除!

杰瑞文章网友情提示:请不要直接抄作文用来交作业。你可以学习、借鉴、期待你写出更好的作文。

春游——科学馆相关的作文:

    无相关信息

说说你对这篇作文的看法吧