前言
前课中,我们一直在使用 Flutter——写 Widget、搭布局、管状态、做导航。但你有没有好奇过:当你写下 Text('Hello') 时,Flutter 到底做了什么,才让这几个字出现在屏幕上?
本文基于官方教程的最后一章「How Flutter Works」,这是一个由 6 个视频组成的深度系列,带你从高层架构一路深入到渲染引擎。本文不涉及代码编写,而是帮你建立完整的心智模型——知道"为什么"往往比知道"怎么做"更重要。
一、Flutter 的整体架构
1.1 声明式 vs 命令式
传统的 UI 开发是命令式的——你需要一步步告诉系统"创建一个按钮、把它放到这里、改变它的颜色"。而 Flutter 是声明式的——你只需描述"界面长什么样",Flutter 负责把描述变成现实。
// 声明式:你描述"我要什么"
// Flutter 负责"怎么实现"
Widget build(BuildContext context) {
return Container(
color: isActive ? Colors.green : Colors.grey,
child: Text('Hello'),
);
}
// 当 isActive 变化时,你不需要手动找到 Container 再改颜色
// 只需调用 setState,Flutter 自动对比差异并更新
1.2 多平台框架
Flutter 不依赖原生平台的 UI 组件。它自己画每一个像素——按钮、文字、滚动条,全部由 Flutter 的渲染引擎绘制。这就是为什么同一份代码能在 iOS、Android、Web、桌面上运行且外观一致。
1.3 Dart 的角色
Flutter 选择 Dart 语言不是偶然的。Dart 有几个关键特性非常适合 UI 框架:AOT 编译(提前编译成机器码,启动快)、JIT 编译(即时编译,支持热重载)、垃圾回收(自动内存管理),以及异步支持(async/await)。
二、三棵树(The Three Trees)
这是理解 Flutter 最核心的概念。你写的每一行 Widget 代码,在 Flutter 内部会经过三棵树的处理:
你写的代码 Flutter 内部维护
│
▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Widget 树 │ ──→ │ Element 树 │ ──→ │ Render 树 │ ──→ 屏幕像素
│ (配置描述) │ │ (生命周期) │ │ (布局绘制) │
└──────────┘ └──────────┘ └──────────┘
轻量级对象 持久化对象 实际绘制
随时可重建 管理更新 计算布局和绘制
2.1 Widget 树:配置描述
Widget 是不可变的配置对象。每次调用 setState,Flutter 会重新调用 build 方法,创建一棵新的 Widget 树。Widget 非常轻量,创建和丢弃的成本很低。
你可以把 Widget 想象成菜单——它描述了"我想要什么",但它本身不是菜。
2.2 Element 树:粘合剂
Element 是 Widget 树和 Render 树之间的粘合层。每个 Widget 在首次构建时会创建一个对应的 Element。Element 是持久化的——当 Widget 重建时,Element 不会被丢弃,而是对比新旧 Widget 的差异,决定是否需要更新 Render 对象。
你可以把 Element 想象成服务员——他拿着菜单(Widget)去厨房(Render)下单,如果客人改了菜(Widget 重建),服务员只会告诉厨房"把这道菜换了",而不是重新下一整桌的单。
2.3 Render 树:实际绘制
RenderObject 负责真正的脏活累活——计算布局(每个组件多大、放在哪里)和绘制像素。Flutter 的布局规则很简单:约束向下流动,尺寸向上传递,父组件设置位置。
约束向下流动:父组件告诉子组件"你最多这么大"
↓
尺寸向上传递:子组件告诉父组件"我实际这么大"
↓
父组件定位:父组件决定子组件放在哪里
这就是为什么 Flutter 的布局系统能快速完成——它只需要一次遍历就能确定所有组件的大小和位置。
三、State 的生命周期
在第 7 课中我们学了 StatefulWidget,但只用了 setState。实际上 State 对象有完整的生命周期:
创建 运行中 销毁
│ │ │
▼ ▼ ▼
initState() ──→ didChangeDependencies() ──→ dispose()
│ │
│ didUpdateWidget()
│ │
└──────────→ build() ←───┘
↑
setState()
各方法的作用:
initState():State 创建时调用一次,适合初始化资源(如创建动画控制器)didChangeDependencies():当依赖的 InheritedWidget(如 MediaQuery、Theme)变化时调用didUpdateWidget():当父组件重建并传入新的 Widget 时调用build():每次需要重绘时调用,返回 Widget 树setState():标记 State 为"脏",触发下一帧重新调用builddispose():State 永久销毁时调用,适合清理资源(如取消网络请求、释放动画控制器)
3.1 关于 const 构造函数的性能优化
// 没有 const:每次 build 都创建新对象
child: Text('Hello')
// 有 const:编译时创建,运行时复用同一个对象
child: const Text('Hello')
// Flutter 检测到 Widget 没变,跳过该子树的重建
这就是为什么官方推荐尽量使用 const 构造函数——它能显著减少 Widget 树重建的范围。
四、RenderObjectWidget
在前面的课程中,我们用过 StatelessWidget 和 StatefulWidget。但它们都不会直接渲染任何东西——它们只是组合和管理其他 Widget 的容器。
真正能在屏幕上画出东西的是 RenderObjectWidget。Flutter SDK 中的 Padding、Align、SizedBox、ColoredBox 等底层组件都继承自 RenderObjectWidget。而 Container 只是把这些底层组件打包在一起的便利组件。
你写的代码 实际绘制的
Container → Padding + ColoredBox + SizedBox + ...
Text → RichText → RenderParagraph
Image → RawImage → RenderImage
作为应用开发者,你通常不需要直接创建 RenderObjectWidget。但了解这一层有助于理解为什么某些 Widget 的行为是这样的。
五、RenderObject 的职责
每个 RenderObject 有四个核心职责:
布局(Layout) :接收父组件传来的约束,计算自己的尺寸,然后为子组件分配位置。这就是 Flutter 文档中常说的"约束向下,尺寸向上,父组件定位"。
绘制(Paint) :在确定了大小和位置后,调用 paint 方法将自己绘制到画布上。绘制是分层的——可以有背景层、内容层和前景层。
命中测试(Hit Testing) :当用户点击屏幕时,Flutter 需要确定点击了哪个组件。RenderObject 通过命中测试从根节点逐层向下查找,确定最终接收触摸事件的组件。
语义(Semantics) :为辅助功能(如屏幕阅读器)提供信息。describeSemanticsConfiguration 方法告诉系统这个组件"是什么"——是按钮、文字还是图片。
六、Flutter 引擎和嵌入器
6.1 Flutter 引擎
Flutter 引擎是用 C++ 编写的,负责最底层的工作:渲染每一帧画面、处理文字排版、管理线程、与操作系统通信。它使用 Skia(或更新的 Impeller)图形库来绘制 UI。
为什么用 C++ 而不是 Dart?因为渲染引擎需要极致的性能,C++ 可以直接操作硬件加速(GPU),而 Dart 更适合编写应用层逻辑。
6.2 嵌入器(Embedder)
Flutter 应用在每个平台上都需要一个"宿主"来启动和运行。嵌入器就是这个宿主——在 Android 上是一个 Activity,在 iOS 上是一个 UIViewController,在 Web 上是一个 Canvas 元素。
嵌入器的职责包括:创建 Flutter 引擎实例、管理应用生命周期、处理输入事件、提供平台服务(如相机、GPS)。
6.3 Platform Channels
Flutter(Dart)和原生平台(Java/Kotlin/Swift/JS)之间通过 Platform Channels 通信。这是一种消息传递机制——Dart 端发送消息,原生端接收并处理,然后返回结果。
Dart 代码 ←── Platform Channel ──→ 原生平台代码
(你的应用) (消息传递管道) (相机、GPS、蓝牙等)
七、完整的渲染流程
当你调用 setState 后,从代码到屏幕像素的完整流程:
1. setState() 被调用
↓
2. State 被标记为"脏"(dirty)
↓
3. 下一帧到来时,Flutter 遍历脏节点
↓
4. 重新调用 build(),生成新的 Widget 树
↓
5. Element 树对比新旧 Widget(diff 算法)
↓
6. 有差异 → 更新对应的 RenderObject
无差异 → 跳过(const Widget 在这里发挥作用)
↓
7. RenderObject 重新计算布局(layout)
↓
8. RenderObject 重新绘制(paint)
↓
9. 渲染引擎(Skia/Impeller)将画面合成
↓
10. GPU 将画面输出到屏幕
↓
用户看到更新后的界面 ✨
整个过程在 16 毫秒内完成(60fps),用户感觉界面更新是"瞬间"的。
八、视频学习资源
本章是一个由 6 个视频组成的系列,建议按顺序观看:
| # | 视频主题 | 核心内容 |
|---|---|---|
| 1 | Flutter's Architecture | 声明式编程、多平台架构、Dart 的角色 |
| 2 | The Three Trees | Widget 树、Element 树、Render 树的协作 |
| 3 | The State Class | State 完整生命周期、const 优化、setState 原理 |
| 4 | The RenderObjectWidget | 哪些 Widget 真正渲染、Container 的真面目 |
| 5 | The RenderObject | 布局、绘制、命中测试、语义四大职责 |
| 6 | Engine and Embedders | C++ 引擎、Skia/Impeller、Platform Channels |
视频链接可在官方教程页面找到:How Flutter Works
九、本节知识点小结
三棵树: Widget 树(配置描述,轻量可重建)→ Element 树(持久粘合层,对比差异)→ Render 树(实际布局和绘制)。
State 生命周期: initState → didChangeDependencies → build → (setState → build) → dispose。理解生命周期有助于正确初始化和释放资源。
布局规则: 约束向下流动,尺寸向上传递,父组件设置位置。一次遍历完成所有布局计算。
渲染引擎: 用 C++ 编写,使用 Skia/Impeller 图形库绘制每一帧。嵌入器负责与各平台的原生环境对接。
const 优化: 使用 const 构造函数可以让 Flutter 跳过未变化的子树重建,显著提升性能。
十、整个系列完结语
恭喜你完成了 Flutter 官方入门教程的全部 17 课!让我们做最后一次回顾:
| 阶段 | 你学到了什么 |
|---|---|
| 第 1-8 课 | 从零搭建环境,理解 Widget 和布局,掌握状态和动画 |
| 第 9-12 课 | 从网络获取数据,用 MVVM 架构管理应用状态 |
| 第 13-16 课 | iOS 风格 UI、自适应布局、高级滚动、页面导航 |
| 第 17 课 | Flutter 的底层原理——三棵树、渲染引擎、State 生命周期 |
你不仅会"用" Flutter,还理解了它"为什么"这样工作。这会让你在遇到问题时能更快地定位原因、在做架构决策时更有信心。
从零基础到理解底层原理,你已经走完了 Flutter 入门的完整路径。接下来,去构建属于你自己的应用吧!