Android 绘制调度机制

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 管最终图层合成。

相关推荐
安卓修改大师2 小时前
安卓修改大师Smali语法实战:从零掌握数据类型、判断循环、自定义方法与Toast插桩
android
私人珍藏库2 小时前
[Android] 多开空间-一机多账号+应用一键克隆双开
android·人工智能·智能手机·软件
海兰2 小时前
【SpringBoot 】AOP企业级权限控制方案(二)
android·java·spring boot
阿pin2 小时前
Android随笔-启动Zygote的rc文件是什么?
android·zygote·rc
帅次12 小时前
Android 高级工程师面试:Java 基础知识 近1年高频追问 22 题
android·java·面试
私人珍藏库13 小时前
[Android] zip解压缩管理-全格式压缩包一键解压+打包
android·app·生活·工具·多功能
雨白13 小时前
C语言:动态内存分配
android
Android-Flutter14 小时前
android compose 自定义Painter绘制图形 使用
android·kotlin·compose