1 为什么UI卡顿,UI卡顿产生的几种原因
1.1 屏幕显示机制

在一个典型的显示系统中,一般包括三个核心部分:
- CPU:负责计算视图数据,包括Measure、Layout、Record DisplayList等操作
- GPU:负责对图形数据进行栅格化(Rasterization)渲染
- Display:负责把处理好的帧数据呈现到屏幕上
Android系统采用固定的刷新频率(通常是60Hz),即每16.6ms刷新一次屏幕。
问题:那么我们的view的onmesuare,layout,draw,是cpu完成还是Gpu完成?
onMeasure
、onLayout
和 onDraw
的【执行】是由 CPU 完成的,但 onDraw
中发出的绘制指令最终是由 GPU 执行的。

卡顿和CPU的关系
CPU 绘制视图树来计算下一帧画面数据的工作是在屏幕刷新信号来的时候才开始工作的,而当这个工作处理完毕后,也就是下一帧的画面数据已经全部计算完毕,也不会马上显示到屏幕上,而是会等下一个屏幕刷新信号来的时候再交由底层将计算完毕的屏幕画面数据显示出来。
界面性能取决于UI 渲染性能,CPU 和GPU 的处理时间因为各种原因都大于一个VSync 的间隔(16.6ms),导致了卡顿。渲染操作通常依赖于两个核心组件:CPU 与GPU。
CPU在渲染管线中负责:
- Measure、Layout计算
- 生成Display List(绘制命令)
- 处理动画计算
- 将数据传递给GPU
如果CPU处理时间超过16.6ms,会导致GPU没有足够时间完成渲染,从而造成掉帧。
1.1.2 双缓冲机制
双缓冲技术是解决画面撕裂的关键机制:
- Back Buffer(后缓冲区):CPU和GPU正在渲染下一帧数据的地方
- Frame Buffer(前缓冲区):屏幕当前正在读取并显示的数据所在地
- 工作流程:当Back Buffer准备就绪后,在VSYNC信号到来时进行缓冲区交换,Back Buffer变成Frame Buffer供屏幕读取
参考:mp.weixin.qq.com/s?__biz=MzA...
保存在两个Buffer 缓冲区中,A 缓冲用来显示当前帧,那么B 缓冲就用来缓存下一帧的数据,
同理,B 显示时,A 就用来缓冲!这样就可以做到一边显示一边处理下一帧的数据。
- GPU:表示GPU合成back buffer的时间段
- Display:显示器读取frame buffer的时间段
1.1.3 三缓存机制
三缓冲在双缓冲基础上增加了一个Back Buffer:
- 优点:当GPU渲染较慢时,CPU可以使用第三个缓冲区开始准备下一帧数据,提高CPU/GPU并行度
- 缺点:增加内存占用,可能带来额外延迟
1.2 什么是卡顿
Android 系统的屏幕刷新频率为 60 fps, 也就是每隔 16 ms 刷新一次。
1.2.1 卡顿的根本原因
卡顿的根本原因是:在16.6ms时间内,系统未能完成下一帧画面的所有计算和渲染工作,导致错过了屏幕刷新时机。
卡顿的本质:就是 16ms不会绘制完一帧,就是卡顿的本质!
从用户角度说,App操作起来缓慢,响应不及时,列表滑动一顿一顿的,动画刷新不流畅等等一些直观感受。
从系统角度来说,屏幕刷新的帧率不稳定,无法保证每秒绘制60帧,也就是说有掉帧的情况发生。
从系统角度表现为:
- 屏幕刷新帧率不稳定,无法保证每秒60帧
- 有掉帧情况发生
- 渲染管线耗时超过16.6ms预算
1.2.2 常见卡顿原因及解决方案
总共产生的原因有3种应用层,1种系统层!
1). 主线程耗时
2)。 绘制
3)。 GC太频繁。导致的内存抖动。导致
4). 系统原因, CPU,GPU渲染导致的!
分析系统原因和应用的原因:系统主要和CPU和GPU有关系!
卡顿类型 | 原因 | 解决方案 |
---|---|---|
主线程耗时 | 网络请求、数据库操作、复杂计算等在主线执行 | 将耗时操作移到子线程,使用协程/RxJava/Executor |
布局复杂 | View树层级过深、单个界面View数量过多 | 使用ConstraintLayout扁平化布局,merge、ViewStub |
过度绘制 | 同一像素区域被多次绘制 | 移除不必要的background,使用clipRect限制绘制区域 |
内存问题 | 内存抖动、内存泄漏导致频繁GC | 避免在循环或频繁调用的方法中创建对象,使用对象池 |
动画性能 | 复杂动画未使用硬件加速 | 使用属性动画并开启硬件加速,考虑使用Lottie |
1.4 卡顿和ANR的关系
假如我在一个button的onClick事件中,有一个耗时操作,这个耗时操作的时间是10秒,但这个耗时操作并不会引发ANR,它只是一次卡顿。
在大部分Android平台的设备上,Android系统是16ms刷新一次,也就是一秒钟60帧。要达到这种刷新速度就要求在ui线程中处理的任务时间必须要小于16ms,如果ui线程中处理时间长,就会导致跳过帧的渲染,也就是导致界面看起来不流畅,卡顿。如果用户点击事件5s中没反应就会导致ANR。
一方面,两者息息相关,长时间的UI卡顿是导致ANR的最常见的原因;
但另一方面,从原理上来看,两者既不充分也不必要,是两个纬度的概念。
- 联系:长时间的UI卡顿是导致ANR的最常见原因
- 区别 :
- 卡顿:渲染帧耗时超过16.6ms,用户感知为操作不流畅
- ANR:应用在5秒/10秒/20秒内未响应输入事件或服务生命周期
- 卡顿不一定导致ANR,ANR也不一定由卡顿引起
1.5 卡顿和内存的关系
- 内存抖动:频繁创建/销毁对象引发GC,GC会暂停所有线程(Stop-The-World)
- 内存不足:当内存不足时,系统GC更频繁,加剧卡顿
- 解决方案:避免在onDraw等频繁调用的方法中创建对象,使用内存缓存
2. 屏幕刷新机制Choreographer源码分析

2.1 源码分析流程图
scss
View.requestLayout()/invalidate()
→ ViewRootImpl.requestLayout()
→ ViewRootImpl.scheduleTraversals()
→ 插入同步屏障(阻塞同步消息)
→ mChoreographer.postCallback(TRAVERSAL, mTraversalRunnable)
→ Choreographer.scheduleFrameLocked()
→ DisplayEventReceiver.scheduleVsync() // 申请VSYNC信号
→ nativeScheduleVsync() // JNI调用底层
// VSYNC信号到来时
DisplayEventReceiver.onVsync()
→ 发送异步消息到主线程Handler
→ MessageQueue取出异步消息执行
→ Choreographer.doFrame()
→ 按顺序执行CALLBACK_INPUT、ANIMATION、TRAVERSAL、COMMIT回调
→ ViewRootImpl.doTraversal()
→ 移除同步屏障
→ ViewRootImpl.performTraversals() // 开始Measure、Layout、Draw
1). ViewRootImpl#requestLayout
csharp
protected ViewParent mParent;
...
public void requestLayout() {
...
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout(); //1
}
}
2). ViewRootImpl#scheduleTraversals
scss
void scheduleTraversals() {
//1、注意这个标志位,多次调用 requestLayout,要这个标志位false才有效
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 2. 同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 3. 向 Choreographer 提交一个任务
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
//绘制前发一个通知
notifyRendererOfFramePending();
//这个是释放锁,先不管
pokeDrawLockIfNeeded();
}
}
Choreographer#scheduleVsyncLocked
arduino
private final FrameDisplayEventReceiver mDisplayEventReceiver;
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
3). DisplayEventReceiver #dispatchVsync()
java
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this); //1 callback是this,会回调run方法
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame); //2
}
}
4). 卡顿第一地方: 如果Handler此时存在耗时操作,那么需要等耗时操作执行完,Looper才会轮循到下一条消息,run方法才会调用,然后才会调用到doFrame(mTimestampNanos, mFrame);
ini
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
...
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
// 1 当前时间戳减去vsync来的时间,也就是主线程的耗时时间
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
//1帧是16毫秒,计算当前跳过了多少帧,比如超时162毫秒,那么就是跳过了10帧
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
// SKIPPED_FRAME_WARNING_LIMIT 默认是30,超时了30帧以上,那么就log提示
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
// 取余,计算离上一帧多久了,一帧是16毫秒,所以lastFrameOffset 在0-15毫秒之间,这里单位是纳秒
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the 8frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
// 出现掉帧,把时间修正一下,对比的是上一帧时间
frameTimeNanos = startNanos - lastFrameOffset;
}
//2、时间倒退了,可能是由于改了系统时间,此时就重新申请vsync信号(一般不会走这里)
if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
//这里申请下一次vsync信号,流程跟上面分析一样了。
scheduleVsyncLocked();
return;
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
//3 能绘制的话,就走到下面
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
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);
}
}
- 不正常, 请求下一帧vsync信号, 计算收到vsync信号到doFrame被调用的时间差,vsync信号间隔是16毫秒一次,大于16毫秒就是掉帧了,如果超过30帧(默认30),就打印log提示开发者检查主线程是否有耗时操作。
2.正常情况: 执行TraversalRunnable()的run()
5). ViewRootImpl$TraversalRunnable
java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
2.2 屏幕刷新机制总结
- UI更新通过
Choreographer
申请VSYNC信号 - VSYNC信号到来时触发
doFrame()
回调 - 按优先级顺序执行输入、动画、遍历、提交四个阶段回调
- 在TRAVERSAL阶段执行View的Measure、Layout、Draw流程
- 渲染完成后通过SurfaceFlinger提交缓冲区并显示
简单的描述:
1). 我要去刷新ui-----View 的 requestLayout 会调到ViewRootImpl 的 requestLayout方法(请求刷新)
2). 然后通过 scheduleTraversals 方法向Choreographer 提交一个绘制任务runable, 但是不会里面执行(存放队列), 同步屏障 (提交请求任务)
3). 在UI线程中, 然后再通过DisplayEventReceiver向底层请求vsync信号, (发vsync信号 )
4).通过JNI回调回来, 收到VSync通知. onVsync(),在doFrame()方法里面, CPU和GPU就立刻开始计算然后把数据写入buffer(doframe)
经常在dofrme()看到一些打印, 请求下一桢
5). docall里面执行之前的run,绘制任务
6).通过Handler移除同步屏障
最终是ViewRootImpl去执行那个绘制任务,调用performTraversals方法,用view的绘制流程,onmeasure(),onlayout(),onDraw();
2.3 源码中得到卡顿的2个根本原因
- 主线程消息队列拥堵 :耗时任务阻塞Looper,导致
doFrame
异步消息不能及时执行 - doFrame执行本身耗时过长:Measure/Layout/Draw计算过于复杂,超过16.6ms
具体的看卡顿的监控原理: 那么有两个地方会造成掉帧 (慢函数监控+ 帧率监控,)
1). (慢函数监控)一个是主线程有其它耗时操作,handler的异步消息没有来得及处理, 导致doFrame,在Choreographer没有机会在vsync信号发出之后16毫秒内调用,对应下图的3;(主线程耗时,导致dofrme没有及时执行)
计算收到vsync信号到doFrame被调用的时间差,vsync信号间隔是16毫秒一次,大于16毫秒就是掉帧了onVsync信号,就是底层16.6s会回调一次过来的! 2次doFrame间隔时间是16.6!
2). (systemTrace)还有一个就是当前doFrame方法耗时,绘制太久,下一个vsync信号来的时候这一帧还没画完,造成掉帧
2.4 卡顿源码重要的类分析
ViewRootImpl
- 连接Window和View的纽带
- 负责管理View树的绘制和输入事件分发
- 通过
scheduleTraversals()
触发绘制流程
Choreographer
Choreographer 两个主要作用
1)、承上:负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等。
2)、启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FrameDisplayEventReceiver.onVsync );请求 Vsync(FrameDisplayEventReceiver.scheduleVsync) .
- 渲染系统的"导演",协调UI刷新节奏
- 负责申请和接收VSYNC信号
- 管理INPUT、ANIMATION、TRAVERSAL、COMMIT四种回调队列
DisplayEventReceiver
- 用于接收VSYNC信号的底层抽象
- 连接应用层和SurfaceFlinger的桥梁
onVsync()
方法在VSYNC信号到来时被调用
3. 相关的VSYNC重点问题
3.1 View是如何显示到屏幕上的
- CPU阶段:Measure → Layout → 生成DisplayList
- GPU阶段:执行DisplayList中的绘制命令,栅格化生成像素数据
- Display阶段:通过SurfaceFlinger合成图层,交换缓冲区,屏幕读取显示
总结
-
CPU进行计算,GPU进行渲染
-
需要绘制,vsync, 然后绘制,3大流程, surfaceView
-
通过sufaceFling合成图片帧
-
给到window,进行显示
3.2 丢帧(掉帧)是延迟显示还是丢弃不再显示?
是延迟显示。当VSYNC信号到来时,如果Back Buffer还没准备好,就无法进行缓冲区交换,屏幕继续显示上一帧内容。准备好的帧会在下一个VSYNC周期显示。
延迟显示,因为缓存交换的时机只能等下一个VSync了。
布局层级较多/主线程耗时 是如何造成 丢帧的呢?
答:布局层级较多, 3个流程会比较慢, 会影响CPU/GPU的执行时间,大于16.6ms时只能等下一个VSync了。
布局层级较多, doFrame里面的方法执行超过16.6ms,就会等下一次VSync信号了
scss
if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
scheduleVsyncLocked();
return;
}
3.3 16.6ms刷新一次是啥意思?是每16.6ms都走一次measure/layout/draw?
- 16.6ms是屏幕硬件的固定刷新周期(60Hz)
- 不是每16.6ms都走measure/layout/draw,只有在需要更新UI时(调用了invalidate/requestLayout)才会触发绘制流程
答:屏幕的固定刷新频率是60Hz,即16.6ms。不是每16.6ms都走一次 measure/layout/draw,而是有绘制任务才会走,并且绘制时间间隔是取决于布局复杂度及主线程耗时。
measure/layout/draw 走完后 会在VSync到来时进行缓存交换和刷新。
measure/layout/draw 走完,界面就立刻刷新了吗?
不是,会等到下一次vsync信号来. 这个下一次指最近一次vsync来的时候.
3.4 如果界面没动静止了,还会刷新吗?
不会 。静止界面没有UI更新请求,Choreographer
不会请求VSYNC信号,doFrame
不会被触发,CPU/GPU处于空闲状态。但屏幕仍然会以固定频率从Frame Buffer读取数据刷新。
答:屏幕会固定每16.6ms刷新,底层会给vsync信号,然后给display,但是不会经过app!
但是,底层还是会每隔 16.6ms 发出一个屏幕刷新信号,只是我们 app 不会接收到而已,Display 还是会在每一个屏幕刷新信号到的时候去显示下一帧画面,只是下一帧画面一直是上一次的帧数据 ,CPU/GPU不走绘制流程。见下面的SysTrace图。
一个静止的页面,刷新1帧和100帧有什么区别?
3.5 VSYNC具体指啥?在屏幕刷新中如何工作的?
- VSYNC(垂直同步信号):由SurfaceFlinger发出,用于同步CPU、GPU和Display的工作节奏
- 作用 :
- 对App:是开始渲染的起跑信号(触发doFrame)
- 对SurfaceFlinger:是进行合成和交换缓冲区的指令
- 来源:可由硬件产生(显示控制器中断)或软件模拟(SurfaceFlinger线程)
答:当扫描完一个屏幕后,设备需要重新回到第一行以进入下一次的循环,此时会出现的vertical sync pulse(垂直同步脉冲)来保证双缓冲在最佳时间点才进行交换。并且Android4.1后 CPU/GPU的绘制是在VSYNC到来时开始。
3.6 Choreographer的作用
答:用于实现------"CPU/GPU的绘制是在VSYNC到来时开始"
- 承上:接收和处理App的各种更新消息和回调
- 启下:负责请求和接收VSYNC信号,协调UI刷新节奏
- 管理四种回调队列:INPUT、ANIMATION、TRAVERSAL、COMMIT
3.7 主线程耗时为什么会导致卡顿?
主线程耗时任务会阻塞Looper,导致Choreographer
发出的异步消息(doFrame)不能得到及时执行,从而错过了本该用于渲染的VSYNC周期。
一个是主线程有其它耗时操作,导致异步消息没有执行, 最后导致doFrame没有机会在vsync信号发出之后16毫秒内调用、
深入理解: 消息机制!
这个原因,系统已经引入了同步屏障消息的机制,尽可能的保证遍历绘制 View 树的工作能够及时进行,但仍没办法完全避免,所以我们还是得尽可能避免主线程耗时工作。
其实第二个原因,可以拿出来细讲的,比如有这种情况, message 不怎么耗时,但数量太多,这同样可能会造成丢帧。如果有使用一些图片框架的,它内部下载图片都是开线程去下载,但当下载完成后需要把图片加载到绑定的 view 上,这个工作就是发了一个 message 切到主线程来做,如果一个界面这种 view 特别多的话,队列里就会有非常多的 message,虽然每个都 message 并不怎么耗时,但经不起量多啊。
这2个是同一个原因还是不同原因?不同, 所以卡顿是2个方面!
问题: 内存抖动为什么会引起卡顿?
3.8 ViewRootImpl与SurfaceFlinger的通信
通过Binder IPC 和共享内存通信:
- 建立连接时通过IWindowSession Binder接口
- 渲染时通过GraphicBuffer共享内存区域
- 通过dequeueBuffer/queueBuffer等IPC调用申请和提交缓冲区
3.9 SurfaceView/GLSurfaceView的底层实现原理
o View 需要在UI 线程对画面进行刷新,而SurfaceView 可在子线程进行页面的刷新
o View 适用于主动更新的情况,而SurfaceView 适用于被动更新,如频繁刷新,这是因为如果使用View 频繁刷新会阻塞主线程,导致界面卡顿
o SurfaceView 在底层已实现双缓冲机制,而View 没有,因此SurfaceView 更适用于需要频繁刷新、刷新时数据处理量很大的页面(如视频播放界面)
SurfaceView特点:
- 拥有独立的Surface和Window
- 可以在非主线程进行绘制
- 底层已实现双缓冲机制
- 内容不在应用窗口上,不能使用变换
与View的区别:
- View必须在UI线程更新,SurfaceView可在子线程更新
- View适用于主动更新,SurfaceView适用于被动更新(如视频播放)
- SurfaceView有双缓冲机制,View没有
3.10 同步屏障的作用
同步屏障用于确保UI绘制消息(异步消息)能优先于普通业务消息(同步消息)执行,是保证UI流畅性的关键机制。
3.11 同步屏障能保证第一时间执行吗?
不能完全保证 。同步屏障是在scheduleTraversals()
时插入的,如果在这之前已经有一个耗时同步消息正在执行,屏障也无法中断它。屏障只能保证在它之后的同步消息被屏蔽。
只能说,同步屏障是尽可能去做到,但并不能保证一定可以第一时间处理。因为,同步屏障是在 scheduleTraversals() 被调用时才发送到消息队列里的,也就是说,只有当某个 View 发起了刷新请求时,在这个
3.12 案例: setTextView会刷新几次
前面我们讲到,当调用updateViewLayout时,就会触发UI刷新绘制流程,例如我们想要修改TextView的值,假如我们连续调用两次setTextView会触发几次重绘呢?
这里我们先了解下Android的刷新机制,Android手机目前设置的帧率为60FPS,也就是每秒60帧的刷新速率,精确到毫秒,就是间隔16ms会绘制一帧,所以即便我们多次调用setTextView,最终也只是会在16ms内触发一次重绘。
那么当UI准备刷新时,到刷新完成中间的流程是什么样的呢?我们以setTextView为例。
当在上层主动调用刷新接口时,一般是通过调用invalidate或者postinvalidate,调用setTextView方法时,底层其实也是调用了invalidate方法申请发起重绘。
那么View自身肯定不能说刷新就能立刻刷新,需要一层一层向上打报告,直到找到ViewRootImpl,此时就像调用ViewManager的updateViewLayout方法一样,会执行ViewRootImpl # scheduleTraversals方法进行重绘,往MessageQueue中插入同步栅栏,同时向VSYNC服务发起请求。
4. 同步屏障与Handler异步消息
4.1 同步屏障机制
java
// 插入同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
4.2 Handler消息类型
- 同步消息:普通消息,按时间顺序执行
- 异步消息:标记为setAsynchronous(true),在同步屏障后优先执行
- 同步屏障消息:特殊的无target消息,用于阻塞后续同步消息
4.3 工作原理
当MessageQueue遇到同步屏障时:
- 跳过所有同步消息,只查找异步消息
- 执行找到的异步消息(如Choreographer发布的doFrame消息)
- 屏障移除后恢复正常消息处理
这种机制确保了UI绘制任务能够优先执行,极大提高了界面流畅性。
4.4 有2个地方发送了异步消息

- 去申请vsync的时候,
2.去 执行doFrame的时候
同步屏障是什么时候插入的?
viewrootImpl调用绘制的时候,!在提交vsync信号之前