Android 绘制调度机制

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