目录前言一、 Vsync信号详解1、屏幕刷新相关知识点2、VSYNC机制二、UI刷新原理流程1、VSYNC流程示意2、view的invalidate3、scheduleTravers
屏幕刷新帧率不稳定,掉帧严重,无法保证每秒60帧,导致屏幕画面撕裂;
今天我们来讲解下VSYNC机制和UI刷新流程
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可能被其他操作占用了,这就是卡顿出现的原因;
当我们通过setText改变TextView内容后,UI界面不会立刻改变,APP端会先向VSYNC服务请求,等到下一次VSYNC信号触发后,APP端的UI才真的开始刷新,基本流程如下:
setText最终调用invalidate申请重绘,最后会通过ViewParent递归到ViewRootImpl的invalidate,请求VSYNC,在请求VSYNC的时候,会添加一个同步栅栏,防止UI线程中同步消息执行,这样做为了加快VSYNC的响应速度,如果不设置,VSYNC到来的时候,正在执行一个同步消息;
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到来后才会被执行;
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();
}
}
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);
}
}
}
}
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);
}
}
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);
}
}
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();
}
}
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的VSYNC机制和UI刷新流程示例详解的详细内容,更多关于Android VSYNC机制UI刷新的资料请关注编程网其它相关文章!
--结束END--
本文标题: Android的VSYNC机制和UI刷新流程示例详解
本文链接: https://lsjlt.com/news/174697.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-01-21
2023-10-28
2023-10-28
2023-10-27
2023-10-27
2023-10-27
2023-10-27
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0