一句话总结
Choreographer 是 “装修监工” ,VSync 是 “定时闹钟” —— 监工(Choreographer)听到闹钟响(VSync信号),就催工人(主线程)赶紧量房(measure)、摆家具(layout)、刷墙(draw),必须在下次闹钟前干完活,否则赶不上送货(掉帧)!
一、VSync:屏幕与App的同步节奏
VSync(Vertical Sync) 是一个由硬件发出的同步信号,它标志着屏幕刷新周期的开始。
-
核心作用:
- 统一节奏:现代设备通常以每秒60次(16.6ms)或更高频率(90Hz/120Hz)刷新屏幕。VSync信号确保App的每一帧绘制都在一个固定的时间窗口内完成,从而避免了“画面撕裂”现象。
- 防止画面撕裂:如果没有VSync,App可能会在屏幕刷新到一半时提交新帧数据,导致屏幕上下部分显示不同帧的内容,形成撕裂感。VSync保证了新帧总是在刷新周期开始时才被提交。
二、Choreographer:UI渲染的精密调度员
Choreographer是Android框架层的一个核心类,它充当了UI渲染的总调度员。它负责在每个VSync信号到来时,安排并执行UI线程上的一系列任务。
-
精细化的回调机制:
-
Choreographer并不直接接收VSync信号,而是向系统显示服务注册。系统服务在每个VSync信号到来后,会按顺序通知Choreographer执行不同类型的回调。 -
这些回调分为多个优先级队列,确保关键任务被优先处理:
- 输入处理(Input Callback) :优先级最高,处理用户输入事件(如触摸、键盘)。
- 动画处理(Animation Callback) :处理属性动画、
ValueAnimator等。 - 绘制处理(Traversal Callback) :优先级最低,负责UI的
measure、layout和draw流程,是导致卡顿最常见的原因。
-
-
如何使用:开发者可以通过
Choreographer.postFrameCallback()方法,在下一个VSync信号到来时,安排一个自定义的回调,用于在正确的时间点更新UI或执行动画。
三、协作流程与性能瓶颈
一个完整的帧渲染过程,是主线程和渲染线程在Choreographer协调下的高效协作。
- VSync信号到达:屏幕刷新周期开始。
Choreographer调度:Choreographer在主线程上执行各种回调。- 主线程工作:
Traversal回调触发**ViewRootImpl** 开始UI绘制流程。这个过程包括measure(测量视图大小)、layout(布局视图位置)和draw(录制绘制指令)。 - 录制指令:在
draw阶段,主线程将所有的绘制指令(如canvas.drawRect)录制成一个DisplayList。 - 渲染线程执行:渲染线程(RenderThread) 接收DisplayList,将其转换为底层的GPU指令,并交给GPU渲染。
-
性能瓶颈:
- 主线程阻塞:如果在
measure/layout/draw阶段,主线程被耗时操作(如IO、复杂计算)阻塞,它将无法在16.6ms内完成DisplayList的录制,导致错过下一个VSync信号,从而掉帧。 - 渲染线程过载:如果
DisplayList过于复杂(如大量透明、阴影效果),渲染线程在执行GPU指令时可能超时,同样会导致掉帧。
- 主线程阻塞:如果在
四、优化实践:让每一帧都在16.6ms内完成
-
主线程优化:
- 避免在UI线程上执行耗时操作:将所有网络请求、数据库查询、大文件读写等任务,转移到子线程中执行。
- 精简布局:使用**
ConstraintLayout** 减少布局嵌套,降低measure和layout的计算时间。 - 视图裁剪:使用
ViewStub来延迟加载不常用的视图,减少启动时的渲染负担。
-
渲染线程优化:
- 减少过度绘制:使用开发者选项中的**“调试GPU过度绘制”**,定位屏幕上被多次绘制的区域。然后通过移除不必要的背景、使用
clipRect等方式来优化。 - 简化
onDraw():避免在onDraw()中执行任何创建对象的操作,因为这会引发不必要的内存分配和垃圾回收,导致性能波动。 - 使用硬件加速:确保View的
onDraw()方法逻辑简单,能够被GPU高效渲染。避免使用复杂的Canvas操作,如setLayerType(),除非万不得已。
- 减少过度绘制:使用开发者选项中的**“调试GPU过度绘制”**,定位屏幕上被多次绘制的区域。然后通过移除不必要的背景、使用