外观
三棵树与页面更新
三棵树各自保存什么
| 层次 | 保存的信息 | 生命周期与职责 |
|---|---|---|
| Widget | 不可变的界面配置 | 构建时可以频繁创建,用于描述想要的 UI |
| Element | 组件在树中的位置、配置关联和依赖 | 管理挂载与更新,BuildContext 由 Element 实现 |
| RenderObject | 布局、绘制、命中测试等所需的信息 | 在配置更新后按需要重新布局或绘制 |
这三层并非逐节点一一对应。StatelessWidget、StatefulWidget 等组合组件通过构建其他 Widget 完成工作,只有对应的渲染组件会直接创建或更新 RenderObject。
可以用 React 的声明式 UI 帮助入门,但 Flutter 的更新与绘制由自己的框架和引擎协作完成。准确理解仍应回到 官方架构说明 和 SDK 源码。
一次状态更新的路径
以点击按钮后执行 setState(() => count++) 为例,阅读时可以按以下顺序设置断点:
State.setState同步执行回调,状态数据发生变化。- 对应 Element 被标记为需要重建,框架安排后续帧处理。
- 构建阶段执行需要更新的
build,生成新的 Widget 配置。 - Element 更新子节点;通常结合位置、
runtimeType和key判断能否复用已有节点。 - RenderObject 接收属性更新,只在需要时进入布局、绘制与合成流程。
因此 setState 并不等于立刻重画整个屏幕。重建范围与 State 的位置、子树结构和依赖有关;新建 Widget 配置也不等于一定创建新的渲染对象。
const 有助于稳定不变的配置,但不会消除所有由依赖变化触发的工作。优化前先记录是哪一阶段耗时,再选择改动。
启动与一帧的工作
应用由 main 进入 runApp,初始化必要绑定并挂载根组件。框架在帧调度下处理构建、布局、绘制、合成与语义等工作,随后由引擎和平台完成后续显示。
| 阶段 | 重点问题 | 调查线索 |
|---|---|---|
| 构建 | 哪些 Element 被标脏?为何重建? | setState、依赖与父级配置更新 |
| 布局 | 约束是什么?尺寸怎样确定? | RenderBox、performLayout、Flex |
| 绘制 | 哪些对象需要重绘? | markNeedsPaint、paint、边界 |
| 合成与栅格化 | 图层如何提交并转成像素? | Layer、Scene、引擎时间线 |
布局规则对应 约束笔记。渲染后端随平台和版本不同,分析时应记录实际环境;不要将所有平台都概括为同一种 Skia 流程。当前后端支持情况参见 Impeller 文档。
从事件到页面变化
指针事件先经过命中测试找到相关对象,手势识别器参与竞技场,最终由适合的回调处理点击或拖动。业务回调再更新状态,进入前面的页面更新路径。
路由侧可以先观察 Navigator 管理的栈、Route 的进入退出及其过渡。带深链接的应用还要关注路由配置如何与外部 URL 对应。实践入口见 事件与路由。
源码阅读清单
源码符号与位置可能随版本变化。先在自己的 Flutter SDK 中搜索符号,再结合断点验证,而不是只记忆一条固定调用链。
| 想回答的问题 | 起点符号 | 常见源码位置 |
|---|---|---|
| 如何启动应用? | runApp、WidgetsFlutterBinding | packages/flutter/lib/src/widgets/binding.dart |
| 状态更新怎样安排重建? | State.setState、Element.markNeedsBuild、BuildOwner.buildScope | packages/flutter/lib/src/widgets/framework.dart |
| 一帧如何调度? | SchedulerBinding、WidgetsBinding.drawFrame | scheduler/binding.dart、widgets/binding.dart |
| 布局和绘制如何执行? | PipelineOwner、RenderObject、RenderBox | rendering/object.dart、rendering/box.dart |
| 手势如何决出响应者? | GestureBinding、GestureArenaManager | gestures/binding.dart、gestures/arena.dart |
| 页面如何入栈与退出? | NavigatorState、Route | widgets/navigator.dart、widgets/routes.dart |
上表后四行省略了共同前缀 packages/flutter/lib/src/。在线阅读可从 Flutter 官方仓库 进入,分析本地问题时则切换到与当前 SDK 一致的版本。
练习与验收
为一个有状态的列表项打印 initState、build 与 dispose,对比没有 Key、索引 Key 和业务 ID Key 时的重排结果。再为一次点击设置 setState 断点,说明哪些节点复用、哪些配置更新,以及为什么不是每次都要重新布局。