Skip to content

Flutter UI渲染分析

· 10 min

声明式编程这回事#

Flutter 是地地道道的声明式 UI:我不命令“你现在去把按钮挪到右边”,而是说“我的界面长这样,这会儿按钮应该在右边”。Widget 就是这份“长这样”的静态配置,天然不可变;真正有生命力的是它对应的 Element(实例化的桥),和真正干活的 RenderObject(布局/绘制高手)。

这套范式的好处是:

一句话:Widget 便宜(配置),Element 聪明(对比/更新),RenderObject 能打(布局/绘制)。

框架的大体骨架#

Flutter 可以粗分两层:

线程模型也值得一提:

两边通过 dart:ui 这层胶水交互。你在 Dart 里画的,最终都变成 Engine 能啃的 Layer 数据。

三棵树:Widget / Element / RenderObject#

很多人会把 Layer 树当作“第四棵树”:当绘制阶段结束,会产出 Layer 树(比如 PictureLayerTransformLayerOpacityLayer)。这层更贴近 Engine,决定了合成边界与复用策略。

一图脑补(文字版):

一帧是怎么跑起来的#

从一次交互到屏幕更新,中间的“帧管线”像打鼓点一样有节奏:

  1. VSYNC 驱动
    SchedulerBinding 接到系统的垂直同步信号(vsync),scheduleFramehandleBeginFrame 开始一帧。

  2. Build 阶段
    BuildOwner 把“脏的” Element 拉出来重建(performRebuild),根据最新的 Widget 配置决定要不要创建/复用子 Element 和 RenderObject。

  3. Layout 阶段
    PipelineOwner.flushLayout 处理被标记 markNeedsLayout 的节点。约束下传、尺寸上收:constraints 从父到子一层层传下去,size 由子往上回报。

  4. Paint 阶段
    PipelineOwner.flushPaint 处理 markNeedsPaint 的节点,调用 paint 把绘制指令写进 Canvas,形成 Picture,并组 Layer 树。

  5. Compositing / Raster
    把 Layer 树交给 Engine,Raster 线程光栅化,最终合成到屏幕。

一个小细节:当你 setState 时,框架通过 ensureVisualUpdate 确保下一帧一定会来,把“该重建的”先登记上,等 vsync 一来,一并结算。

布局与绘制的细枝末节#

setState 背后的机关#

setState 的逻辑并不神秘,但很精妙:

顺手提下生命周期里几个“常被忽略的关键点”:

InheritedWidget 的“广播式”更新#

InheritedWidget 像是“无声的扩音器”:子树通过 context.dependOnInheritedWidgetOfExactType 建立依赖,一旦上游新旧值 updateShouldNotify 返回 true,所有依赖者会在下一帧重建。
这也是 Provider 等状态库的基石:省去手写全局事件分发,天然按子树范围精准更新。

RepaintBoundary 与合成层#

RepaintBoundary 是性能杀手锏:

判断是否重绘:
子树内容没变、但父节点移动/裁剪变化,通常只改合成,不重画像素。恰当的 Layer 结构,能“搬桌子不打翻菜”。

为什么有时 setState 看着“不生效”?#

几种常见误会:

Key:给 Element 一个身份标签#

列表重排是 Key 的高发地带。没有 Key,框架会按位置复用 Element;一旦顺序变化,就可能“把 A 的状态放到 B 身上”。

工程向的几条“土方法”#

// 小例子:异步回调里安全 setState
if (!mounted) return;
setState(() {
counter++;
});

一个完整的心理模型#

小结#

写到这儿,感觉 Flutter 的“画画哲学”很接地气:你只负责描述世界,至于“怎么把它高效地画出来”,交给框架和引擎去抠细节就行。只要搞懂这条渲染链路,定位问题、做优化,都会顺手多了。