Choreographer、ViewRootImpl 与 Android 绘制调度机制
1. 这一课的核心问题
Android 的 UI 更新并不是:
text
View 状态变化
↓
立刻 draw
↓
马上显示到屏幕
真实机制更接近:
text
View 状态变化
↓
invalidate / requestLayout
↓
ViewRootImpl.scheduleTraversals
↓
Choreographer 等待 Vsync
↓
主线程执行一帧回调
↓
ViewRootImpl.performTraversals
↓
measure / layout / draw
↓
提交绘制结果
↓
SurfaceFlinger 合成显示
核心思想是:
Android 不让 View 想什么时候画就什么时候画,而是让 UI 更新跟随屏幕刷新节奏,在合适的 Vsync 时机统一执行一帧绘制。
2. 为什么 invalidate 不会立刻 draw?
当调用:
java
view.invalidate();
它并不会马上执行 draw()。
invalidate() 的语义是:
这个 View 的显示内容脏了,需要在下一帧重新绘制。
它做的是"申请绘制",不是"立即绘制"。
如果每次 View 状态变化都立刻 draw,会有几个问题:
text
1. 多次 UI 更新无法合并,容易重复绘制。
2. App 绘制节奏和屏幕刷新节奏不一致。
3. 可能画得太早,SurfaceFlinger 还没到合成时机。
4. 可能画得太晚,错过当前帧。
5. 输入、动画、布局、绘制无法被组织成一个稳定的一帧流程。
所以 Android 选择:
UI 更新先标记脏状态,再等下一次 Vsync,由 Choreographer 统一调度一帧。
3. Choreographer 是什么?
Choreographer 可以理解为:
App 主线程上的帧调度器。
它不是负责真正绘制 View 的对象。
真正执行 View 树遍历和绘制的是:
text
ViewRootImpl.performTraversals()
Choreographer 的职责是:
text
1. 接收 Vsync 节奏。
2. 在主线程上调度一帧。
3. 按阶段组织 Input、Animation、Traversal、Commit。
4. 在合适时机通知 ViewRootImpl 执行 traversal。
可以这样理解角色边界:
text
View:
负责具体 UI 内容和 draw 逻辑。
ViewRootImpl:
负责连接 View 树、Window、Surface,并执行 measure / layout / draw。
Choreographer:
负责根据 Vsync 调度一帧。
MessageQueue:
负责承载主线程消息,包括 Choreographer 的帧回调。
SurfaceFlinger:
负责系统级图层合成,把多个 Surface 合成到屏幕。
一句话:
Choreographer 不画图,它负责告诉主线程:这一帧该开始干活了。
4. Vsync 是什么?
Vsync 可以理解为:
显示系统提供的屏幕刷新节奏信号。
例如:
text
60Hz 屏幕:大约 16.6ms 一帧
120Hz 屏幕:大约 8.3ms 一帧
Android 的 UI 绘制要尽量贴合这个节奏。
如果不用 Vsync,而是用普通的:
java
handler.postDelayed(drawRunnable, 16);
会有问题:
text
1. 16ms 只是估算,不等于真实刷新时机。
2. 不同设备刷新率不同。
3. 高刷 / 动态刷新率下,固定延迟不准确。
4. Handler 延迟可能和显示节奏漂移。
5. App 绘制和 SurfaceFlinger 合成可能错开。
所以 Android 使用:
text
Vsync 驱动 Choreographer
Choreographer 驱动一帧 UI traversal
而不是用普通定时器驱动 UI。
5. View.invalidate 的主链路
调用:
java
view.invalidate();
大致链路是:
text
View.invalidate()
↓
View.invalidateInternal()
↓
向父 View 传播脏区域
↓
最终到 ViewRootImpl.invalidateChildInParent()
↓
ViewRootImpl.scheduleTraversals()
重点是:
text
ViewRootImpl.scheduleTraversals()
它不是立刻执行绘制,而是安排一次 traversal。
6. ViewRootImpl.scheduleTraversals 做了什么?
ViewRootImpl.scheduleTraversals() 大致做两件关键事:
text
1. 向 MessageQueue 插入同步屏障。
2. 通过 Choreographer 注册 CALLBACK_TRAVERSAL,等待下一次 Vsync。
简化链路:
text
ViewRootImpl.scheduleTraversals()
↓
MessageQueue.postSyncBarrier()
↓
Choreographer.postCallback(CALLBACK_TRAVERSAL, mTraversalRunnable)
↓
等待 Vsync
↓
Choreographer.doFrame()
↓
TraversalRunnable.run()
↓
ViewRootImpl.doTraversal()
↓
MessageQueue.removeSyncBarrier()
↓
ViewRootImpl.performTraversals()
所以:
scheduleTraversals 的本质是:告诉系统"下一帧我要做一次 View 树遍历和绘制"。
7. 什么是 traversal?
Traversal 可以理解为:
View 树的一次完整遍历过程。
它通常包括:
text
measure
layout
draw
也就是:
text
1. 测量 View 多大。
2. 摆放 View 在哪里。
3. 把 View 画出来。
对应到 ViewRootImpl 里,就是:
text
ViewRootImpl.performTraversals()
↓
performMeasure()
↓
performLayout()
↓
performDraw()
不是每次 traversal 都一定完整执行 measure / layout / draw。
具体执行哪些阶段,要看 View 树状态:
text
requestLayout:
通常意味着尺寸或布局关系变了,可能需要 measure + layout + draw。
invalidate:
通常意味着显示内容脏了,主要需要 draw。
8. requestLayout 和 invalidate 的区别
invalidate
invalidate() 表示:
View 的显示内容变了,需要重绘。
例如:
text
颜色变化
文字内容变化
进度变化
背景变化
自定义 View 内容变化
它主要影响:
text
draw 阶段
requestLayout
requestLayout() 表示:
View 的尺寸、位置或布局关系可能变了,需要重新布局。
例如:
text
宽高变化
margin 变化
padding 变化
LayoutParams 变化
子 View 数量变化
父子布局关系变化
它通常影响:
text
measure
layout
draw
所以一般来说:
text
requestLayout 比 invalidate 更重。
Framework 视角下:
invalidate 主要解决"内容需要重画",requestLayout 主要解决"布局结构需要重新计算"。
9. Choreographer.doFrame 的执行阶段
当 Vsync 到来后,Choreographer 会在主线程执行:
text
doFrame(frameTimeNanos)
一帧里大致分为几个阶段:
text
CALLBACK_INPUT
CALLBACK_ANIMATION
CALLBACK_TRAVERSAL
CALLBACK_COMMIT
可以理解为:
text
1. Input:先处理输入事件。
2. Animation:推进动画状态。
3. Traversal:执行 View 树遍历,进行 measure / layout / draw。
4. Commit:提交这一帧相关结果。
为什么要这样分阶段?
因为一帧应该基于最新输入和动画状态来绘制。
合理顺序是:
text
先处理用户输入
↓
再更新动画状态
↓
再根据最新状态布局和绘制
↓
最后提交结果
如果 Vsync 一来就直接 draw,就可能出现:
text
1. 输入事件还没处理,画出来的是旧状态。
2. 动画状态还没推进,画面不连续。
3. 布局和绘制缺乏统一调度。
所以 Choreographer 的意义不是简单"定时器"。
它是:
按显示刷新节奏组织主线程 UI 工作的调度中心。
10. ViewRootImpl.doTraversal 做了什么?
当 Choreographer 的 CALLBACK_TRAVERSAL 被执行后,会调用:
text
TraversalRunnable.run()
↓
ViewRootImpl.doTraversal()
doTraversal() 的核心逻辑是:
text
1. 移除同步屏障。
2. 执行 performTraversals。
简化链路:
text
ViewRootImpl.doTraversal()
↓
MessageQueue.removeSyncBarrier()
↓
ViewRootImpl.performTraversals()
↓
performMeasure()
↓
performLayout()
↓
performDraw()
为什么要先移除同步屏障?
因为同步屏障只是为了让这一帧 traversal 能优先被执行。
当 traversal 已经开始执行时,屏障就应该移除。
否则普通同步消息会一直被挡住,导致主线程普通消息无法继续执行。
11. 同步屏障和绘制调度的关系
同步屏障是 MessageQueue 里的特殊消息。
普通消息一般有 target:
text
msg.target = Handler
同步屏障的特点是:
text
msg.target = null
它的作用是:
阻挡普通同步消息,让异步消息优先通过。
当 ViewRootImpl 请求绘制时,会插入同步屏障:
text
MessageQueue.postSyncBarrier()
这样可以避免绘制相关的异步消息被普通 Handler 消息一直挡住。
Choreographer 的 Vsync / frame 相关消息属于异步消息,可以越过同步屏障。
所以这套机制可以理解为:
text
ViewRootImpl:
我需要下一帧绘制,所以先插一个屏障。
MessageQueue:
屏障之后,普通同步消息先别急。
Choreographer:
等 Vsync 到来后,用异步消息越过屏障,执行这一帧。
ViewRootImpl:
开始 traversal 后,移除屏障,继续让普通消息执行。
核心目的:
让绘制调度在合适时机获得更高优先级,避免被普通同步消息长期阻塞。
12. 异步消息不是后台线程消息
异步消息不是说它会跑到子线程。
它仍然在同一个 Looper 线程执行。
异步消息的特殊点是:
当 MessageQueue 中存在同步屏障时,异步消息可以越过屏障执行。
如果没有同步屏障:
text
同步消息和异步消息基本都按 when 排序。
如果有同步屏障:
text
普通同步消息会被挡住。
异步消息可以继续被取出执行。
所以异步消息的重点不是"异步执行",而是:
在 MessageQueue 的屏障机制下拥有通行权。
13. 一次 UI 绘制的完整主链路
可以总结成:
text
App 修改 UI 状态
↓
View.invalidate / requestLayout
↓
ViewRootImpl.scheduleTraversals()
↓
MessageQueue.postSyncBarrier()
↓
Choreographer.postCallback(CALLBACK_TRAVERSAL)
↓
等待 Vsync
↓
Vsync 到来
↓
Choreographer.doFrame()
↓
执行 CALLBACK_INPUT
↓
执行 CALLBACK_ANIMATION
↓
执行 CALLBACK_TRAVERSAL
↓
TraversalRunnable.run()
↓
ViewRootImpl.doTraversal()
↓
MessageQueue.removeSyncBarrier()
↓
ViewRootImpl.performTraversals()
↓
performMeasure()
↓
performLayout()
↓
performDraw()
↓
提交 buffer
↓
SurfaceFlinger 合成
↓
屏幕显示
这条链路背后的核心设计是:
App 不直接控制屏幕刷新。App 按 Vsync 节奏生产图像内容,SurfaceFlinger 再负责把多个 Surface 合成到屏幕。
14. Choreographer 和 ViewRootImpl 的边界
Choreographer 负责调度
它关心的是:
text
什么时候开始这一帧
这一帧有哪些阶段
Input / Animation / Traversal / Commit 按什么顺序执行
如何跟 Vsync 对齐
ViewRootImpl 负责执行 View 树遍历
它关心的是:
text
View 树是否需要 measure
View 树是否需要 layout
View 树是否需要 draw
Window 和 Surface 如何关联
绘制结果如何提交给后续渲染管线
所以两者关系是:
text
Choreographer:
按 Vsync 通知"该干活了"。
ViewRootImpl:
真正执行"怎么干活"。
更简洁地说:
Choreographer 管节奏,ViewRootImpl 管执行。
15. 为什么不能只靠 Handler.postDelayed 做绘制?
如果用:
java
handler.postDelayed(drawRunnable, 16);
来模拟一帧,会有几个问题:
text
1. 16ms 不等于真实 Vsync。
2. 不同刷新率设备下帧间隔不同。
3. 动态刷新率下,固定 delay 更不可靠。
4. Handler 消息可能和显示系统节奏漂移。
5. 无法天然组织 Input、Animation、Traversal 的阶段顺序。
6. 多次 UI 更新不容易自然合并成一帧。
所以 Android 用的是:
text
Vsync
↓
Choreographer
↓
ViewRootImpl traversal
而不是:
text
Handler 定时器
↓
直接 draw
面试里可以这样说:
Handler.postDelayed 只能根据时间延迟投递消息,但不能感知真实显示刷新节奏。Choreographer 基于 Vsync 调度一帧,可以适配不同刷新率,并把输入、动画、View 树遍历和提交阶段组织到同一帧中执行,减少无效绘制和节奏漂移。
16. 面试可复述版本
Android 的 UI 更新不是 invalidate() 后立刻 draw(),而是先通过 ViewRootImpl 申请一次 traversal。
当 View 调用 invalidate() 或 requestLayout() 后,请求会逐步传递到 ViewRootImpl。ViewRootImpl 会调用 scheduleTraversals(),向 MessageQueue 插入同步屏障,并通过 Choreographer 注册 traversal 回调,等待下一次 Vsync。
Vsync 是显示系统提供的刷新节奏信号。Choreographer 是 App 主线程上的帧调度器,它基于 Vsync 组织一帧的执行顺序,包括 Input、Animation、Traversal、Commit。真正执行 View 树 measure、layout、draw 的不是 Choreographer,而是 ViewRootImpl 的 performTraversals()。
同步屏障是 MessageQueue 里一种特殊消息,target 为 null。它会阻挡普通同步消息,但允许异步消息越过。Choreographer 的帧回调相关消息是异步消息,所以可以在有同步屏障时优先执行,避免绘制请求被普通 Handler 消息一直挡住。
这套设计的本质是:主线程 MessageQueue 里承载了很多任务,但 UI 绘制必须尽量和屏幕刷新节奏对齐。因此 Android 用 Vsync 驱动 Choreographer,用 Choreographer 调度一帧,用同步屏障和异步消息保证 traversal 在合适时机优先执行,最后由 ViewRootImpl 真正完成 View 树的 measure、layout、draw。
17. 本节核心记忆
text
Looper:
让主线程具备消息循环能力。
MessageQueue:
承载主线程消息,并支持同步屏障和异步消息。
Choreographer:
基于 Vsync 调度一帧,组织 Input / Animation / Traversal / Commit。
ViewRootImpl:
连接 View 树、Window、Surface,真正执行 performTraversals。
SurfaceFlinger:
负责系统级图层合成,最终显示到屏幕。
最关键一句话:
Choreographer 管一帧的节奏,ViewRootImpl 管 View 树的遍历和绘制,SurfaceFlinger 管最终图层合成。