Flutter原理深度解析:从架构到渲染的全面剖析(二)

一、概述

这篇博客主要介绍Flutter中的三棵树。

二、Flutter中的三棵树

这是 Flutter 最核心的原理之一,理解这三棵树的关系至关重要。

1. Widget 树(配置描述)

Widget 是不可变的配置信息,它描述了 UI 应该长什么样。每次 setState() 调用都会重建 Widget 树。

1.Widget的本质

Widget的源码如下:

dart 复制代码
@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({this.key});
  final Key? key;

  @protected
  @factory
  Element createElement();

  @override
  String toStringShort() {
    final String type = objectRuntimeType(this, 'Widget');
    return key == null ? type : '$type-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  @override
  @nonVirtual
  bool operator ==(Object other) => super == other;

  @override
  @nonVirtual
  int get hashCode => super.hashCode;


  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
  }


  static int _debugConcreteSubtype(Widget widget) {
    return widget is StatefulWidget
        ? 1
        : widget is StatelessWidget
        ? 2
        : 0;
  }
}

从源码中,我们可以看出源码顶层标记 @immutable 不可变注解,且所有属性(Key)全部为 final 修饰。这是 Flutter 核心设计:Widget 是纯配置模板,禁止运行时修改,保证新旧组件比对的准确性,为 Diff 算法、局部刷新提供底层支撑。

然后我们看到有一个createElement 方法,Widget类是一个抽象类,定义了核心抽象方法 createElement(),由子类实现:StatefulWidget 创建 StatefulElement,StatelessWidget 创建 StatelessElement。是Widget 绑定 Element、初始化三棵树的唯一入口。

还有一个重要的方法是canUpdate方法,静态方法 canUpdate(oldWidget, newWidget) 是 Flutter 组件复用的核心规则:只有 runtimeType 一致 + Key 一致,才复用旧组件,否则销毁重建。

2.Widget的种类

|--------------------|-------------------|--------------------------------|
| 类型 | 特点 | 典型代表 |
| StatelessWidget | 无状态,纯展示 | Text、Icon、Container |
| StatefulWidget | 持有 State,可变 | Checkbox、TextField、ListView |
| RenderObjectWidget | 直接关联 RenderObject | Opacity、Transform、 CustomPaint |

3.Widget的轻量级性验证

我们可以使用官方的计时器统计下创建Widget的耗时。

复制代码
void main() {
  final stopwatch = Stopwatch()..start();
  for (int i = 0; i < 100000; i++) {
    Text('Hello $i');
  }
  stopwatch.stop();
  debugPrint('10 万次创建耗时: ${stopwatch.elapsedMilliseconds}ms');
}

执行上述的代码之后,控制台输出的时间为10ms(一般在20-~50ms),也就是我们创建10万个Widget耗时为10ms。

这说明Widget 本质上就是一个普通 Dart 对象,只有几个字段,GC 友好。你可以放心地在

build() 里返回全新的 Widget 树,性能瓶颈从来不在 Widget 创建上。

4.const 构造函数的威力

const 构造函数让 Widget 在编译期就确定,运行时完全复用同一实例:

在下面的代码中:

复制代码
class MyWidget extends StatelessWidget {
const MyWidget({super.key}); // ← const 构造
@override
Widget build(BuildContext context) {
return const Text('Hello'); // ← const 实例,全局唯一
}
}
// 在父组件中:
Widget build(BuildContext context) {
return const MyWidget(); // 永远是同一个实例
// → Element.updateChild() 中 identical(old, new) == true
// → 直接 return child,跳过整个 diff 过程
}

const Widget 在 diff 时会被 identical() 检测为同一实例,框架直接跳过更新。这是 Flutter 最

廉价、最有效的性能优化手段。养成「能 const 就 const」的习惯。

2. Element 树(生命周期实体)

Element 是 Widget 在树中的实例化,它管理着 Widget 的更新和生命周期。当 Widget 树重建时,Flutter 会复用对应的 Element(如果 runtimeTypekey 相同),从而高效更新 UI。

1.Element的核心职责

|---------|-------------------------|--------------------------|
| 职责 | 方法 | 说明 |
| 持有引用 | _widget, renderObject | 绑定 Widget 和 RenderObject |
| 生命周期 | mount/unmount | 控制组件挂载和卸载 |
| Diff 比对 | updateChild() | 决定复用/更新/销毁 |
| 重建调度 | markNeedsBuild() | 将自己加入脏列表 |
| 依赖管理 | dependOnInheritedWidget | InheritedWidget 订阅 |

2.Element的核心属性

abstract class Element {

Element? _parent; // 父 Element

Widget? _widget; // 当前绑定的 Widget

RenderObject? renderObject; // 对应的渲染节点

bool _active = true; // 是否在树中活跃

int _depth = 0; // 树的深度

List<Element>? _children; // 子 Element 列表

}

3.Diff 算法:updateChild 全流程

这是 Flutter 渲染最核心的算法,决定了 Element 是否复用。

updateChild 是 Flutter 单节点 Diff 的核心源码方法,位于 ElementsetStaterebuildperformRebuild 执行 build 拿到新 Widget 之后,就调用updateChild对子节点做差异化更新。 注意:updateChild处理单个子节点;列表多子节点走updateChildren。

源码简化版:

复制代码
// child:旧的Element;newWidget:新生成的Widget;newSlot:节点在父中的位置标记
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
  // Case1:新Widget为null → 需要移除子节点
  if (newWidget == null) {
    if (child != null) {
      deactivateChild(child); // 停用旧Element,脱离树,加入_inactiveElements
    }
    return null;
  }

  // newWidget不为null
  if (child != null) {
    // Case2:新旧Widget是同一个对象(const组件经常命中)
    if (child.widget == newWidget) {
      if (child.slot != newSlot) updateSlotForChild(child, newSlot);
      return child; // 直接复用Element,**不调用update,子树完全跳过更新**
    }

    // Case3:canUpdate 判断:runtimeType + key同时相等,可以复用Element
    if (Widget.canUpdate(child.widget, newWidget)) {
      if (child.slot != newSlot) updateSlotForChild(child, newSlot);
      child.update(newWidget); // 更新Element内部的widget引用
      return child;
    }

    // Case4:不能复用,销毁旧Element
    deactivateChild(child);
  }

  // Case5:child==null,或者上面不能复用 → 创建全新Element
  return inflateWidget(newWidget, newSlot);
}

|------------------|--------------|--------------------|
| 条件 | 结果 | 说明 |
| 同类型 + 同 key | ✅ 复用 Element | 最常见,正常更新路径 |
| 同类型 + 不同 key 变了, | ❌ 不复用 key | 当作全新组件 |
| 不同类型 | ❌ 不复用 Widget | 种类变了,必须重建 |
| 同一实例 (identical) | ✅ 直接跳过 | const Widget 的最优路径 |

4.Element的挂载流程

mount():把新建的 Element 加入 Element树,完成三棵树初始化的核心入口。 调用起点:inflateWidgetinflateWidget 要么是 runApp 初始化,要么是 updateChild 中需要新建节点时调用。

前置:inflateWidget(创建+挂载的入口)

复制代码
Element inflateWidget(Widget newWidget, Object? newSlot) {
  // GlobalKey 优先从_inactiveElements复用旧Element
  if(newWidget.key is GlobalKey){
    final Element? reuseElem = _retakeInactiveElement(newWidget.key);
    if(reuseElem!=null){
      reuseElem._activateWithParent(this, newSlot);
      return updateChild(reuseElem, newWidget, newSlot);
    }
  }
  // 1. 通过Widget.createElement() 创建Element对象
  final Element newChild = newWidget.createElement();
  // 2. 执行mount,挂载到父Element,slot标记位置
  newChild.mount(this, newSlot);
  return newChild;
}

slot:标记该节点在父中的位置,Row/Column列表子节点靠slot记录顺序;单节点一般为null。

mount基础父类逻辑 Element.mount

复制代码
void mount(Element? parent, Object? newSlot) {
  _parent = parent;       // 绑定父Element,加入Element树
  _slot = newSlot;
  _lifecycleState = _ElementLifecycle.active; // 标记为激活状态
  if(parent != null){
    _owner = parent._owner; // 继承BuildOwner,管理脏节点
  }
}

mount做两件基础事:绑定父节点、标记生命周期为active,子类再扩展各自逻辑。

Element有两大子类,ComponentElement、RenderObjectElement,mount逻辑完全不一样。

1.ComponentElement

这里ComponentElement包含(StatelessElement / StatefulElement,组合组件)。

复制代码
@override
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  _firstBuild(); // 首次构建
}

void _firstBuild() {
  rebuild(); // 调用performRebuild
}

@override
void performRebuild() {
  Widget? builtWidget;
  if(this is StatefulElement){
    // StatefulElement:触发 initState → didChangeDependencies
    builtWidget = state.build();
  }else{
    // StatelessElement:直接build
    builtWidget = widget.build();
  }
  // 递归!调用updateChild 处理子Widget,继续inflateWidget+mount子Element
  _child = updateChild(_child, builtWidget, null);
}

StatefulElement 额外关键点 mount过程中会实例化State对象:

  1. StatefulElement构造函数执行 state = widget.createState()
  2. 赋值 state._element = this,State绑定在Element上,不是Widget
  3. mount → _firstBuildrebuildperformRebuild
  4. 执行 state.initState()(只mount阶段执行一次!)
  5. 执行 state.didChangeDependencies()
  6. 调用 state.build() 获取子Widget
  7. updateChild递归挂载子节点

✅ Stateful生命周期顺序: createState() → mount() → initState() → didChangeDependencies() → build()

2.RenderObjectElement

这里的RenderObjectElement包含(Text、Image,真正渲染组件)。

复制代码
@override
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  // 1. 创建RenderObject对象,建立Element ↔ RenderObject映射
  _renderObject = widget.createRenderObject(this);
  // 2. 将RenderObject attach挂载进RenderObject渲染树
  attachRenderObject(newSlot);
  _dirty = false;
  // 如果有子节点,继续updateChild递归子树
}
  • createRenderObject:由RenderObjectWidget实现,生成真正负责布局绘制的RenderObject;

  • attachRenderObject:把_renderObject插入父RenderObject的子列表,Render树正式构建完成。

重点:只有RenderObjectElement才有RenderObject;ComponentElement没有RenderObject。

📌完整调用链路(runApp初始化一棵树)

复制代码
runApp(rootWidget)
  → RenderObjectToWidgetAdapter.attachToRenderTree
    → rootWidget.createElement() 创建RootElement
    → rootElement.mount(null, null) 根节点mount,parent为null
        → ComponentElement.mount
          → _firstBuild() → rebuild() → performRebuild
              → build()拿到子widget
              → updateChild
                  → inflateWidget
                      → childWidget.createElement()
                      → child.mount(parentElement, slot) 【递归】
                          ├─ ComponentElement:继续_firstBuild/build/updateChild
                          └─ RenderObjectElement:createRenderObject + attachRenderObject

递归:父mount过程中,会完成全部子节点的createElement + mount,深度优先构建完整三棵树。

挂载完整时序(Stateful组件示例)

  1. widget.createElement() → new StatefulElement,内部执行widget.createState()实例化State
  2. element.mount(parent, slot)
    • super.mount:绑定父、标记active
    • _firstBuild()rebuild()performRebuild()
    • 调用state.initState() 【仅mount执行一次】
    • 调用state.didChangeDependencies()
    • 调用state.build()拿到子Widget
    • updateChild()处理子Widget
      • 子节点走inflateWidgetcreateElementmount()(深度优先递归子树)
  3. 如果是RenderObjectElement类型子节点:创建RenderObject + attachRenderObject加入渲染树。

深度优先:父mount,然后完整挂载所有子节点,子全部mount完毕,父mount才算完成

mount阶段和update阶段的区别

mount(挂载,新建节点) update(更新,复用Element)
触发来源 inflateWidget,第一次创建节点 updateChild中canUpdate=true,复用Element
Stateful 执行initState() 不会执行initState,执行didUpdateWidget
RenderObject 调用createRenderObject、attachRenderObject 调用updateRenderObject,不重建RenderObject
_firstBuild 执行 不会执行

initState只在mount挂载的时候执行一次;后续父rebuild复用Element,只会走update,不会再次initState。

3. RenderObject 树(渲染实体)

RenderObject 负责实际的布局和绘制计算。它拥有尺寸、位置等几何信息,并通过 paint() 方法调用 Skia 进行绘制。布局(Layout)和绘制(Paint)是渲染管线中最耗时的阶段。

渲染流程:构建 Widget 树 → 创建/更新 Element 树 → 创建/更新 RenderObject 树 → 布局(Layout) → 绘制(Paint) → 合成(Compositing) → 上屏(Rasterizing)。

1.RenderObject 的核心方法

|---------------------|------|----------------|
| 方法 | 阶段 | 职责 |
| performLayout() | 布局 | 计算自身尺寸 + 约束子节点 |
| paint() | 绘制 | 往 Canvas 上画内容 |
| hitTest() | 命中测试 | 判断点击是否落在自己区域内 |
| describeSemantics() | 无障碍 | 描述语义信息 |
| markNeedsLayout() | 标记 | 请求重新布局 |
| markNeedsPaint() | 标记 | 请求重新绘制 |

2.布局协议:约束向下,尺寸向上

Flutter 采用单向数据流布局模型:父节点向子节点传递约束(Constraints),子节点向父节

点返回尺寸(Size)。

3.自定义RenderObject 实战

通过CustomPaint + CustomPainter 可以介入绘制层:

Dart 复制代码
class CirclePainter extends CustomPainter {
final Color color;
final double radius;
CirclePainter({required this.color, required this.radius});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
radius,
paint,
);
}
@override
bool shouldRepaint(CirclePainter old) {
return old.color != color || old.radius != radius;
}
}
// 使用:
CustomPaint(
size: Size(200, 200),
painter: CirclePainter(color: Colors.blue, radius: 50),
child: Center(child: Text('Hello')),
)

4.RenderObject的生命周期

Dart 复制代码
// 1. Element mount 时创建
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this); // ← 创建
attachRenderObject(newSlot); // ← 挂到渲染树
}
// 2. Widget 更新时同步
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject!); // ← 更新
}
// 3. Element unmount 时销毁
void unmount() {
widget.didUnmountRenderObject(renderObject!); // ← 清理
super.unmount();
}

2.RenderObject 的核心方法

相关推荐
里欧跑得慢3 小时前
CSS 模块化架构的演进:BEM、CSS Modules 到 CSS-in-JS 的反思
前端·css·flutter·web·css-in-js
iFlyCai12 小时前
深入理解Flutter:StatefulWidget生命周期全解析(一)
flutter·dart·状态管理·statefulwidget
xiangxiongfly91514 小时前
Flutter video_player总结
flutter·video_player·chewie
见山是山-见水是水1 天前
鸿蒙版 Flutter Video 视频播放组件:播放控制、全屏切换与倍速播放
flutter·音视频·harmonyos
六年码农1 天前
2026最新开源 屏译ScreenTranslator 0.4.4 开源 全屏实时翻译教程
运维·人工智能·flutter·开源
WeiAreYoung1 天前
Flutter+iOS 实现 Live Activity
flutter·ios
恋猫de小郭2 天前
超好用 R8 Configuration Analyzer, 优化你的 App 大小和内存
android·前端·flutter
键盘飞行员2 天前
Flutter App 全套实战学习计划:从零搭建到 APK 部署
学习·flutter
GitLqr2 天前
Flutter 实战:图片上传前的压缩优化,解决带宽与存储的“大户”问题
flutter·全栈·dart