it编程 > App开发 > Android

Android Compose 绘制原理全面总结

14人参与 2026-09-16 Android

android compose 的绘制原理可以概括为一句话:‌通过 kotlin 编译器插件将声明式 ui 代码转换为高效的 slottable 操作,运行时再经组合、布局、绘制三阶段单向流水线,最终交给 renderthread 和 gpu 完成光栅化合成上屏。‌ 这背后是 ui = f(state) 的核心思想——你只描述“界面应该长什么样”,compose 负责在状态变化时精确计算最小更新范围。

两篇材料共同描绘了 jetpack compose 的绘制体系:它不是普通 kotlin 库,而是一套由声明式 ui、kotlin 编译器插件、slottable 运行时、三阶段渲染流水线、rendernode/renderthread 自绘系统共同组成的全新架构。其核心公式可以概括为:

ui = f(state)

开发者只描述“ui 应该是什么样”,compose 运行时负责在状态变化时计算出最小更新范围,并依次完成组合、布局、绘制,最终交给 gpu 合成上屏。

一、根本范式转变:从命令式 view 到声明式 compose

传统 android view 体系是命令式的。开发者需要手动调用 settext()setvisibility()invalidate()requestlayout() 等方法,直接修改 ui 对象。ui 是一棵由 view / viewgroup 组成的可变对象树,通常由 xml 定义,再通过继承和手动属性设置完成界面。

compose 则彻底转向声明式。ui 是状态的函数,开发者只负责描述当前状态下界面应该长什么样。状态变化后,compose 运行时自动计算差异并更新屏幕,不需要手动寻找控件、修改属性。它摒弃了基于 xml 和继承的传统视图树,采用“声明式 ui + 编译器插件 + 自绘系统”的全新架构。

这种转变带来的直接结果,是更新粒度和性能模型的改变:compose 可以做到细粒度更新,而不是传统 view 那样频繁触发整树遍历和手动刷新。

二、编译期:kotlin compiler plugin 做了什么

compose 高度依赖 kotlin 编译器插件。@composable 并不是普通注解,它会触发一系列编译期转换。

1.@composable函数转换

编译器会把所有 @composable 函数转换为带有额外参数的普通函数。最核心的是注入:

也就是说,compose 的声明式代码并不是运行时“解释执行”的,而是在编译期就被改写成了高效的状态操作指令。

2. slottable 生成

编译器会把组合逻辑转化为对 slot table(槽表) 的操作指令,例如:

slottable 是 compose 存储 ui 树状态、记忆化数据和组合结构的核心数据结构。它让 compose 能够记住上一次组合的结果,并在下次重组时精确知道哪些部分需要更新。

3. 智能跳过与 skip generation

编译器会分析参数的稳定性(stability),自动生成相等性检查代码。如果参数未变,就可以直接跳过该函数的重组。这就是 compose 智能跳过机制的编译期基础。

此外,编译器还会插入 restart groupsreplaceable groups,用于支持重组时的局部重执行和结构替换。

三、运行时三阶段流水线

compose 的渲染管线分为三个严格分离的阶段:

composition(组合)→ layout(布局)→ drawing(绘制)

这三个阶段单向流动,是理解 compose 性能和绘制原理的主线。

1. composition:组合阶段

组合阶段执行 @composable 函数,把 ui 描述为一棵可组合节点树,也可以理解为 composition tree 或 layoutnode 树。这个阶段产出的是逻辑树和 emit nodes,不涉及任何布局坐标或像素绘制

当状态变化时,compose 不会重新执行整个函数树,而是通过 slottable 精确定位受影响的节点,进行局部重执行。这就是 recomposition(重组)

重组是智能的:compose 会跟踪每个 composable 读取了哪些 state。状态变了,只重组读取它的函数,跳过无关部分。跳过机制由编译器插入的 restart groups 和 replaceable groups 实现。

组合阶段的跳过条件是:读取的 state 未变,且参数相等。参数相等由编译器自动插入比较逻辑完成。

2. layout:布局阶段

布局阶段负责测量和放置。它遵循严格的单向数据流:

compose 的关键特点是单次测量(single-pass measurement)。布局树只允许遍历一次,不能像传统 view 系统那样重复 measure。这保证了布局复杂度为 o(n),避免了 view 体系中多次 measure-pass 带来的性能问题。

modifier 链上的 layout() 等修饰符也参与这套测量协议,因此 modifier 顺序非常重要。例如:

两者的点击区域不同,因为 padding 改变了向下传递的约束和最终可点击范围。

同时,compose 严禁在 measure 中读取其他节点的尺寸,也严禁在 place 中触发重组。这些限制保证了布局可以在单次遍历中完成。

布局阶段的产出,是每个节点在屏幕上的确切坐标和大小,形成最终的 layoutnode 布局信息。

3. drawing:绘制阶段

绘制阶段把 layoutnode 转换为实际渲染指令。每个节点拿到 canvas / drawscope,可以执行:

绘制内容会被记录为绘制命令列表(display list),帧到来时回放执行。

绘制顺序通常是:父节点先绘制背景(drawbehind),再画子节点内容,最后画前景(drawfront)。

底层实现上,compose 并不像传统 view 那样每个 view 直接逐层调用 android canvas api。它更接近构建一棵 displaylist / rendernode 树,类似 recyclerview 的显示列表机制。每帧通过 androidcanvas 等桥接方式,把 draw 命令下发到底层 rendernode / displaylist,最终提交给 android 的 renderthread,由 gpu 完成合成上屏。

这意味着大部分绘制操作可以脱离主线程(ui thread),极大减少掉帧风险。compose 还会自动管理硬件图层,避免不必要的离屏缓冲。

动画方面,compose 由 monotonicframeclock 驱动,它基于 choreographer,按 vsync 逐帧触发:

recomposition → layout → draw

从而保证动画和滚动的流畅性。

四、为什么快:跳过机制与关键优化

compose 每一帧的核心逻辑是“能不重算就不重算”。三个阶段的跳过条件可以总结为:

阶段跳过条件
composition读取的 state 未变,且参数相等
layout测量约束未变,且子节点布局未变
drawing绘制内容未变,绘制指令缓存和离屏缓存可复用

这也是 modifier.drawbehind 和自定义 drawscope 性能好的原因:当阶段输入没变时,整棵子树的绘制可以被整体跳过。

除了三阶段跳过,compose 还有几个关键优化机制:

机制原理效果
structural equality编译器插入结构化相等检查参数不变则跳过重组
snapshot state system基于 mvcc 的状态管理系统精确追踪 state 读取者,仅通知相关组件重组
positional memoization基于调用位置而非 key 的记忆化比 react/vue 的 virtual dom diff 更快,无 diff 开销
lazy layouts仅组合可见项 + 预取策略列表滚动流畅度媲美原生 recyclerview
draw phase isolation绘制与组合、布局完全解耦纯视觉变化可跳过前两个阶段

这些机制共同构成了 compose 细粒度更新、单遍布局和异步渲染的基础。

五、绘制到底怎么画到屏幕上

把前面的流程串起来,compose 一帧的完整路径大致是:

  1. compose 生成一棵 layoutnode 树,每个节点持有 modifiers 链;
  2. 状态变化触发重组,slottable 精确定位受影响节点;
  3. 布局阶段单次遍历,确定每个节点的尺寸和位置;
  4. 绘制阶段把绘制指令记录为 display list;
  5. 每帧通过 androidcanvas 把 draw 命令下发到底层 rendernode / displaylist
  6. 最终交给 gpu 合成显示,走 hardwarerenderer,本质和 view 系统的 renderthread 类似;
  7. 动画由 monotonicframeclock 基于 choreographer 驱动,按 vsync 逐帧触发三阶段。

因此,compose 的绘制本质可以概括为:

编译器把声明式代码转化为高效的 slottable 操作 → 运行时通过三阶段流水线把状态映射为 rendernode → 最终由 renderthread/gpu 完成光栅化和合成。

六、与传统 android view 体系的对比

特性jetpack compose传统 android view 系统
ui 构建理念声明式,ui 是状态的函数命令式,ui 是可变 view 树
核心阶段组合 → 布局 → 绘制测量 → 布局 → 绘制
渲染载体layoutnode 树,最终走 rendernode/displaylistview / viewgroup 树,每个 view 负责 ondraw
更新机制智能重组,自动追踪状态,仅更新变化部分手动调用 setter、requestlayout、invalidate
测量方式单次测量,o(n),禁止重复 measure可能多次 measure,父子约束传递更复杂
布局约束约束自顶向下,尺寸自底向上,严格单向类似,但允许重复测量和更复杂的自定义行为
绘制线程大量工作可交给 renderthread/gpu,主线程压力更小主线程 ondraw 压力更大,依赖 renderthread 但 view 层级更重
性能特点按需更新,跳过无变化阶段,减少不必要计算每次帧刷新通常遍历整棵 view 树,即使内容未变
diff 机制positional memoization,无 virtual dom diff 开销无统一 diff,需手动更新
列表性能lazy layouts 仅组合可见项 + 预取recyclerview 复用机制,成熟但配置复杂

compose 从根本上解决了传统 view 体系的若干性能瓶颈:多次 measure、手动 invalidate、整树遍历、命令式更新带来的状态不一致风险。

七、性能实践要点

根据两篇材料,可以提炼出以下实践建议:

纯视觉变化尽量放在绘制阶段,避免触发组合和布局。

八、与 view 体系的互操作

compose 并不是完全独立于 android 平台,它与传统 view 体系可以互相嵌入:

这种互操作能力让 compose 可以渐进式接入现有项目,而不是要求一次性重写全部 ui。

九、总结

综合两篇材料,compose 绘制原理可以浓缩为一句话:

compose = 声明式描述 ui + 三阶段单向流水线 + 基于状态追踪的细粒度跳过 + 编译器 slottable + rendernode/renderthread gpu 合成。

它通过 kotlin 编译器插件把 @composable 转换为高效的 slottable 操作;运行时通过 composition、layout、drawing 三阶段,把状态映射为 rendernode;最终由 renderthread 和 gpu 完成光栅化与合成。

相比传统 view 体系,compose 省掉了多次 measure 遍历和手动 invalidate 的开销,实现了细粒度更新、单遍布局和异步渲染。它的核心优势不是“画得更快”这么简单,而是从编译期到运行时、从状态管理到渲染管线,整体重构了 android ui 的更新模型。

到此这篇关于android compose 绘制原理全面总结的文章就介绍到这了,更多相关android compose原理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

您想发表意见!!点此发布评论

推荐阅读

Android ChipGroup 简介及使用说明

09-16

Android多渠道打包与Gradle构建优化实战

09-06

Android常用设计模式速查一览表

09-05

Android App启动速度优化完整流程(冷启动/热启动全链路实战)

07-31

Android普通网络和EAP网络的WiFi连接完整过程

07-28

Android系统应用蓝牙分享文件失败的分析解决方法

07-28

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论