当前位置:网站首页>vie的刷新机制
vie的刷新机制
2022-06-24 23:12:00 【东东旭huster】
前言
平时只知道在调用invalidate, requestLayout,或者 通过动画能够刷新屏幕,实现想要的ui效果,但是对view的刷新机制并不了解,本文记录了我对以下几个问题的思考和总结。
- view多长时间刷新一次
- view什么时机刷新
- view如何刷新
- 如果界面不变化,还需要刷新吗
- 只要调用invalidate, requestLayout等函数就会会被立即刷新吗
view多长时间刷新一次
Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity).
为什么是16ms, 因为Android设定的刷新率是60FPS(Frame Per Second), 也就是每秒60帧的刷新率, 约合16ms刷新一次.
对于一个系统来说,可以分为CPU,GPU和显示器三个部分,CPU负责计算,GPU对计算的数据进行渲染,然后放到缓冲区中存起来,显示器每隔一个固定的频率去取渲染好的数据显示出来。显示器刷新频率是固定的,但是CPU和GPU的计算和渲染时间却是没有规律的,假设GPU的渲染速率是瞬间完成的,那主要的时间因素就取决于CPU,CPU计算的过程其实就是View树的绘制过程,即从根布局开始,遍历所有的view分别执行测量、布局、绘制的过程,如果我么的界面过于复杂,在16ms内没有计算完成,那显示器取到的就是旧的数据,这就是掉帧,对用户来说就会觉得卡顿。
上图就是发生了一次典型的掉帧过程。
view刷新时机
我们都知道invalidate(), requestLayout()函数会让view进行重绘,但是一旦调用就立即开始重新绘制了吗?上面提到的VSYNC信号有什么作用?
我们从View的invalidate函数开始看看整个执行流程
public void invalidate(Rect dirty) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
dirty.right - scrollX, dirty.bottom - scrollY, true, false);
}
invalidateInternal()函数中会递归的调用parent的invaldateChild()函数,最终会调用到ViewRootImpl的invalidate()方法
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 插入一个同步消息屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mTraversalRunnable是一个Runnable对象,它内部执行了doTraversal()方法,这个方法中才真正的开始遍历和绘制操作,从上面代码中可以知道,它并不是立即开始绘制,而是通过mChoreographer对象注册了一个回调,在这个postCallback中会请求同步VSYNC信息。
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
// 申请同步信号
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
因为加了同步消息屏障,所以这里创建了一个异步的MSG_DO_FRAME消息,这里要看一个很重要的类FrameDisplayEventReceiver
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
private VsyncEventData mLastVsyncEventData = new VsyncEventData();
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource, 0);
}
// TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
// the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
// for the internal display implicitly.
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#onVsync " + vsyncEventData.id);
}
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
// earlier than the frame time, then the vsync event will be processed immediately.
// Otherwise, messages that predate the vsync event will be handled first.
long now = System.nanoTime();
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
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;
mLastVsyncEventData = vsyncEventData;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}
}
这个类会接收到底层的VSYNC信号,VSync信号由SurfaceFlinger实现并定时发送,FrameDisplayEventReceiver收到信号后,调用onVsync方法组织消息发送到主线程处理。这个消息主要内容就是run方法里面的doFrame()方法。也就是说,我们必须先提前注册,底层的VSYNC信号产生时才能回调到我们的app中,否则接受不到这个信号。
所以说,当我们调用了 invalidate(),requestLayout(),等之类刷新界面的操作时,并不是马上就会执行这些刷新的操作,而是通过 ViewRootImpl 的 scheduleTraversals() 先向底层注册监听下一个屏幕刷新信号事件,然后等下一个屏幕刷新信号来的时候,才会去通过 performTraversals() 遍历绘制 View 树来执行这些刷新操作。
总结
综上,可以总结出
- 只有同步信号VSYNC到来界面才会刷新
- UI如果没有变化,则不会请求同步信号,界面不会刷新
- 同步信号VSYNC需要申请才会有。
- 同一帧内,如果有多个重绘的请求,scheduleTraversals() 会将其过滤掉,只需要安排一次绘制任务就行了,在下一次VSYNC信号到来时才会调用**performTraversals()**遍历view树并重绘。
- 为了保证绘制任务优先被执行,ViewRootImpl会插入一个同步消息屏障,同步消息不会被处理,以此尽可能的保证在接收到VSYNC信号后能够第一时间进行处理。
边栏推荐
- Folding screen will become an important weapon for domestic mobile phones to share the apple market
- Kaggle 专利匹配比赛金牌方案赛后总结
- QT package the EXE file to solve the problem that "the program input point \u zdapvj cannot be located in the dynamic link library qt5cored.dll"
- Investigation on key threats of cloud computing applications in 2022
- The ecosystem of the yuan universe
- Resolution of cross reference in IDA
- 一线城市软件测试工资——你拖后腿了吗
- leecode学习笔记-机器人走到终点的最短路径
- 【STL源码剖析】STL六大组件功能与运用(目录)
- 高数 | 精通中值定理 解题套路汇总
猜你喜欢
PyTorch学习笔记(七)------------------ Vision Transformer
Lizuofan, co-founder of nonconvex: Taking quantification as his lifelong career
Network planning | [four network layers] knowledge points and examples
Redis
Can automate - 10k, can automate - 20K, do you understand automated testing?
Distributed transaction solutions and code implementation
记一次beego通过go get命令后找不到bee.exe的坑
AI clothing generation helps you complete the last step of clothing design
记一次beego通过go get命令后找不到bee.exe的坑
qt打包exe文件,解决“无法定位程序输入点_ZdaPvj于动态链接库Qt5Cored.dll”
随机推荐
【STL源码剖析】STL六大组件功能与运用(目录)
当一个接口出现异常时候,你是如何分析异常的?
DDD concept is complex and difficult to understand. How to design code implementation model in practice?
F - spices (linear basis)
Centos7.3 modifying MySQL default password_ Explain centos7 modifying the password of the specified user in MySQL
psql 列转行
Intranet learning notes (6)
Talking about the advantages of flying book in development work | community essay solicitation
高速缓存Cache详解(西电考研向)
[analysis of STL source code] functions and applications of six STL components (directory)
高数 | 精通中值定理 解题套路汇总
Summary of knowledge points of computer level III (database) test preparation topics
记一次beego通过go get命令后找不到bee.exe的坑
Viewing MySQL password on Linux_ MySQL forgets password "suggestions collection" under Linux
internship:svn的使用
保险APP适老化服务评测分析2022第06期
1-6搭建Win7虚拟机环境
Planification du réseau | [quatre couches de réseau] points de connaissance et exemples
3年测试经验,连简历上真正需要什么都没搞明白,张口就要20k?
折叠屏将成国产手机分食苹果市场的重要武器