本文正在参加「金石方案 . 瓜分6万现金大奖」
前语
屏幕改写帧率不稳定,掉帧严峻,无法确保每秒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);
<!--增加一个处理触摸事情的回调,避免中间有Touch事情过来-->
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) {
<!--恳求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);
}
}
- 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树遍历;
8、doTraversal
// 这儿是真实履行了,
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
<!--移除同步栅门,只有重绘才设置了栅门,说明重绘的优先级仍是挺高的,一切的同步音讯有必要让步-->
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;
}
10、制作总结
- android最高60FPS,是VSYNC及决定的,每16ms最多一帧;
- VSYNC要客户端自动恳求,才会有;
- 有VSYNC到来才会改写;
- UI没更改,不会恳求VSYNC也就不会改写;
总结
关于制作还有许多常识点,后面会总结陆续宣布来的;