本节摘要:本节是全章的物理底图。先纵切三层——Framework(Dart 写的界面世界)、Engine(C++ 写的图形核心)、Embedder(各平台宿主壳),再横切时间——一帧从垂直同步信号出发,经构建、布局、绘制、合成、光栅化六道工序在预算内上屏。读完你将能对任何一次卡顿给出第一手的定位假设。
第 1 章说过宿主与画布的分工,这里把引擎内部也摊开。Flutter 的运行时自上而下分三层,每层的语言、职责、出错特征都不同:
Framework 层(Dart):你日常打交道的全部——Widget、手势、动画、主题、状态管理都在这一层,它是纯 Dart 写的,随你的应用一起打包。性能问题里"重建过多""布局爆炸"一类,病根在这层,治法也在这层:改你的代码或用法。
Engine 层(C++):图形核心。文本排版与光栅化、图形后端(Impeller,以及仍在逐步退场的 Skia)、合成器、Dart 运行时(含 Isolate 调度与垃圾回收)都在这里。这一层你改不了,但它的行为可观测——DevTools 里的时间线大多是这层和 Framework 层对话的记录。
Embedder 层(平台语言):第 1 章说的宿主壳。Android 上是一段接入 Activity 生命周期的代码,iOS 上接入 UIApplication,桌面接入各自的窗口系统。平台通道的"平台侧"就住在这层,第 7 章会正式打开它。
分层的价值在于故障定位有方向:界面显示错——查 Framework(你的 Widget 树);所有内容都渲染但整体卡顿掉帧——查管线各工序耗时;应用起不来、窗口出不来——查 Embedder 与宿主配置;文字全是豆腐块、图形撕裂——才是 Engine 侧的问题。

管线的六道工序不是挤在一个线程里跑的,线程归属本身就是诊断的坐标系:
线索串起来就是:你的代码只直接占用平台主线程(经通道)与 UI 线程(build 与逻辑);光栅线程的账虽然常被你的写法间接放大(图层多、重绘范围大),但你从不直接在它上面写代码——这也是"光栅线程红先查结构、UI 线程红先查代码"口诀的由来。
屏幕以固定节奏刷新(60Hz 即约 16.7 毫秒一帧,高刷屏预算更紧),每次刷新前引擎收到垂直同步信号,管线开跑。工序顺序固定,谁超时谁背锅:
构建:框架从标记为"脏"的位置出发执行 build,产出新的 Widget 配置树。这一步只做描述,不碰像素。build 里写循环、算大数、同步读盘,超时账全记在这道工序头上。
布局:约束从根节点向下传递("你最大多大"),尺寸从叶向上汇报("我就要这么大"),一趟遍历完成。第 3 章的布局规则就是这道工序的外显。布局是递归的,嵌套越深越贵,这也是"列表项别嵌几十层"的由来。
绘制:需要重绘的部分把"画圆、画字、画阴影"这类指令记录到图层上。注意它记录的是指令而非像素,真正的像素在最后一道工序产生。
合成与光栅化:Engine 把图层组装成场景,交给图形后端光栅化成位图。Impeller 的改进就发生在这里——它在管线构建期就预编译好着色器,砍掉了 Skia 时代首次遇到某种效果时现场编译着色器造成的小卡顿(当年动画第一次播放顿一下,多半是它)。
诊断时用得上的一句话账法:UI 线程超时看构建与布局,光栅线程超时看绘制指令量与图层结构。DevTools 的性能视图正是按这两个线程分开记账的,第 6 章会实操。
下面的页面故意在 build 里同步睡掉 50 毫秒,跑在真机上即可复现一次可测量的掉帧:
import 'dart:io'; import 'package:flutter/material.dart'; class JankDemo extends StatelessWidget { const JankDemo({super.key}); void burnFrame() { // 反面教材:在 build 路径上同步阻塞,只做演示 sleep(const Duration(milliseconds: 50)); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ElevatedButton( onPressed: () { burnFrame(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('这一帧超时了')), ); }, child: const Text('制造一次卡顿'), ), ), ); } }
按下按钮的瞬间,界面明显一顿——50 毫秒的活儿挤进 16 毫秒的预算,本帧作废,下一帧补画。打开 DevTools 的 Performance 页,时间线上会有一段醒目的红色帧条。修法预告:把这类耗时逻辑挪出 build(第 2.4 节的 Isolate),或者至少拆成多帧分摊。此刻先记住结论:卡顿不是"手机慢",是某道工序超了预算,而工序是可以点名批评的。
管线的"构建"工序里出现了 Widget 树的影子。下一节把三棵树摊开,解释为什么重建一棵树很便宜、而重算几何才昂贵。