深入剖析:Flutter 应用从启动到渲染的完整旅程
上一篇文章中介绍了一个全新的 Flutter 应用是如何被创建的,那么一个创建好的 Flutter 应用又是如何运行起来的呢?具体来说,当你运行 flutter run 命令,手机上就运行了一个 Flutter 应用,这一切又是如何发生的呢?这就是我们今天要讲的主题。
一个跨平台框架是极其复杂的,要在一篇文章中把 Flutter 的工作机制讲清楚有不少的挑战。故本文将忽略一些非主线流程,且微调一些流程的发生顺序,使得读者阅读起来更轻松。
Flutter 架构概览
一个典型的 Flutter 项目,项目根目录下有对应的 iOS 和 Android 两个目录,分别存放了 iOS 和 Android 平台的原生代码。这些原生代码是 Flutter 项目的一部分,尽管它们只是一个空壳,但 Flutter 项目的启动过程离不开这些原生代码。也就是说尽管 Flutter 是自渲染不依赖于原生平台的组件绘制界面,但它仍然离不开原生平台的支撑。
这里可以用寄生关系来表示原生平台和 Dart 之间的关联。原生平台是宿主,负责启动 App、提供原生平台能力等,Dart 应用是寄生物,负责生成绘制界面的指令、执行用户的业务逻辑等。基于此,我们可以把 Flutter 的运行机制分为以下几个核心步骤:
- 启动原生应用
- 初始化 Flutter Engine
- 编译并加载 Dart 代码
- 执行 Dart 代码并构建 Widget 树
- 生成渲染指令并传递给引擎
- 引擎执行渲染并输出到屏幕
第一步:启动原生应用
本文中我们以 iOS 端应用为例,做过 iOS 开发的朋友都知道,要想运行一个 iOS 项目,打开 Xcode,点击 Run 按钮,应用程序就会在模拟器或真机上启动。这原本是一件很简单的事情,但对于 Flutter 开发而言,事情就没有这么简单,原因是基本上很少有 Flutter 开发者直接使用 Xcode 开发 Flutter 应用。由于苹果生态的封闭性,要想开发运行 iOS 应用基本上无法绕过 Xcode,而 Flutter 开发者却大多又是通过 VSCode 等其他编辑器开发应用。那如何解决这个问题呢,答案是 Flutter 框架是通过脚本拉起 Xcode,运行 iOS 原生应用。我们来看看 Flutter 具体是如何实现的。
_processManager.start(
cmd,
workingDirectory: workingDirectory,
environment: _environment(allowReentrantFlutter, environment),
mode: mode,
);
这里 cmd 参数值如下:
/usr/bin/arch -arm64e
xcrun osascript -l JavaScript
flutter/packages/flutter_tools/bin/xcode_debug.js
debug
--xcode-path /Applications/Xcode.app
--project-path /project-dir/ios/Runner.xcodeproj
--workspace-path /project-dir/ios/Runner.xcworkspace
--project-name Runner
--expected-configuration-build-dir /project-dir/build/ios/iphoneos
--device-id 00008120-000864CA3692201E
--scheme Runner
--skip-building
--launch-args ["--enable-dart-profiling"]
ProcessManager.start 的作用是启动一个外部进程(例如运行 shell 命令或可执行文件),并返回一个 Future<Process> 对象,供 Flutter 工具链管理进程的执行、输入输出和退出状态。它是 Flutter 工具(如 flutter run、flutter build)内部用来与操作系统交互的核心组件之一。
/usr/bin/arch -arm64e 表示在 macOS 上强制使用指定的处理器架构(此处是 arm64e)来执行后面的命令,这里我们再裁剪掉一些参数,就相当于是执行了下述命令:
xcrun osascript -l JavaScript xcode_debug.js
xcrun是 Xcode 的命令行工具,用于执行 Xcode 相关的工具链命令。osascript是 macOS 中用来执行 AppleScript 或 JavaScript 脚本的命令。-l JavaScript参数指定使用 JavaScript 作为脚本语言,而不是默认的 AppleScript。
如果你是第一次接触这些命令可能还不太理解,这里我们介绍一下相关的知识点:AppleScript 是 Apple 在 1990 年代初开发的自动化脚本语言,专为控制 macOS 应用程序和系统功能而设计,而 JavaScript for Automation (JXA) 是 Apple 引入的 AppleScript 替代方案。osascript 是 macOS 内置的命令行工具,用于执行 AppleScript 和 JavaScript for Automation (JXA)。也就是说 Flutter 是通过 osascript 执行 JavaScript for Automation (JXA) 脚本拉起 Xcode,运行原生应用的,具体怎么对应的命令如下:
const actionResult = targetWorkspace.debug({
scheme: args.targetSchemeName,
skipBuilding: args.skipBuilding,
commandLineArguments: args.launchArguments,
});
这里 targetWorkspace 是一个 document 对象,debug 方法的作用是使用"运行"或"不构建直接运行"的方案操作来启动调试会话,这就等同于手动打开 Xcode,点击 Run 按钮,在真机或模拟器上启动应用程序。
第二步:初始化 Flutter Engine
原生应用启动后,第一件事就是初始化 Flutter Engine。Flutter Engine 是 Flutter 的核心组件,使用 C++ 编写,主要负责:
- Skia 图形库的封装 - 提供 2D 图形渲染能力
- Dart 运行时的管理 - 创建和管理 Dart VM
- 平台通道的实现 - 处理 Dart 与原生代码的通信
- 线程管理 - 管理 UI、GPU、IO、Platform 等线程
在 iOS 平台上,Flutter Engine 的初始化过程如下:
// FlutterEngine 初始化
- (instancetype)initWithName:(NSString*)labelPrefix
project:(FlutterDartProject*)project
allowHeadlessExecution:(BOOL)allowHeadlessExecution {
_labelPrefix = [labelPrefix copy];
_allowHeadlessExecution = allowHeadlessExecution;
_project = project ?: [[FlutterDartProject alloc] init];
// 创建 Shell
_shell = flutter::Shell::Create(
flutter::PlatformData{},
flutter::TaskRunners{
_labelPrefix.UTF8String,
fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform
fml::MessageLoop::GetCurrent().GetTaskRunner(), // raster
fml::MessageLoop::GetCurrent().GetTaskRunner(), // ui
fml::MessageLoop::GetCurrent().GetTaskRunner() // io
},
settings,
[](flutter::Shell& shell) {
return std::make_unique<flutter::PlatformViewIOS>(shell);
}
);
return self;
}
这里最核心的是创建了 flutter::Shell 对象,Shell 是 Engine 的核心类,它协调了四个重要的线程:
- Platform Thread(平台线程):运行原生代码,处理平台事件
- UI Thread(UI 线程):执行 Dart 代码,构建 Layer Tree
- Raster Thread(光栅化线程):将 Layer Tree 光栅化成 GPU 指令
- IO Thread(IO 线程):处理图片解码等 IO 密集型任务
第三步:编译并加载 Dart 代码
启动了空壳原生应用后,界面上仍旧什么都看不到。要想在屏幕上把应用界面都渲染出来,那就需要执行 Dart 代码了。那么如何执行 Dart 代码呢?
自 Dart 2 版本起,虚拟机(VM)不再具备直接从原始源代码执行 Dart 的能力。取而代之的是,VM 期望接收包含序列化 Kernel 抽象语法树(AST)的 Kernel 二进制文件(也称为 dill 文件)。也就是说,我们需要提前把 Dart 代码编译成 dill 文件,然后启动虚拟机执行 dill 文件。
生成 dill 文件
当我们通过 flutter run 命令运行 Flutter 应用时,Flutter 会把 Dart 代码编译为 dill 文件。核心代码如下:
_processManager.start(command);
其中 command 参数值如下:
flutter/bin/cache/dart-sdk/bin/dartaotruntime \
flutter/bin/cache/dart-sdk/bin/snapshots/frontend_server_aot.dart.snapshot \
--sdk-root flutter/bin/cache/artifacts/engine/common/flutter_patched_sdk/ \
--incremental \
--target=flutter \
--experimental-emit-debug-metadata \
--output-dill /var/folders/gj/zrn2sp9134xdh4bbfz6c87sc0000gn/T/flutter_tools.arBZn4/flutter_tool.9H0eht/app.dill \
--packages /project-dir/.dart_tool/package_config.json \
-Ddart.vm.profile=false \
-Ddart.vm.product=false \
--enable-asserts \
--track-widget-creation \
--filesystem-scheme org-dartlang-root \
--initialize-from-dill build/cache.dill.track.dill \
--source file:///project-dir/.dart_tool/flutter_build/dart_plugin_registrant.dart \
--source package:flutter/src/dart_plugin_registrant.dart \
-Dflutter.dart_plugin_registrant=file:///project-dir/.dart_tool/flutter_build/dart_plugin_registrant.dart \
--verbosity=error \
--enable-experiment=alternative-invalidation-strategy
这里再裁剪掉 command 里面的一些不重要的路径参数等,就相当于是执行了下述命令:
dartaotruntime frontend_server_aot.dart.snapshot --arguments
dartaotruntime 命令是干什么的呢?在 Dart 编程中,你可以创建被称为 AOT 快照的预编译 Dart 应用程序。dartaotruntime 可以运行 AOT 快照,在这里也就是运行 frontend_server_aot.dart.snapshot。
现在我们已经知道了 dartaotruntime 是 Dart 的一种运行时,上面的一行命令是做什么的也很容易理解:执行 frontend_server_aot.dart 文件,并传入一些相关参数。那 frontend_server_aot.dart 文件又是干什么的呢?在编译原理中,前端(Frontend)和后端(Backend)是编译器设计的两个主要阶段,它们分别负责不同的任务。前端处理源代码的分析和理解,生成中间表示,后端将中间表示优化并转换为目标平台的代码。我们从 frontend_server_aot.dart 文件名就可以看出,它承担的角色就是编译原理中的前端部分。也就是说 frontend_server_aot.dart 会把我们编写的 Dart 代码编译成 app.dill 文件(从参数可以看出),dill 文件是 Dart 语言中的一种中间表示格式,也称为 Kernel binaries。
本来到这里 Dart 代码的预编译已经介绍完了,但既然已经提到了 frontend_server_aot.dart 文件,就顺便提一下在 Dart 语言中编译前端阶段可以玩出什么新的花样。我们知道 Dart 语言是不支持面向切面编程(AOP)的,但闲鱼技术团队却开发出了针对 Dart 的 AOP 编程框架 AspectD,背后的原理就是通过定制化 Dart 语言中编译前端,这个阶段完全可以对源代码进行修改。那为什么不在运行时动态修改代码呢?原因是 Flutter 框架中禁止使用 Dart 反射功能。
加载 dill 文件到 Dart VM
生成好 dill 文件后就可以交给 Dart 虚拟机来执行了。那么 Dart 虚拟机又是如何启动的呢?
在 Flutter Engine 初始化完成后,会创建 Dart VM,并加载 dill 文件:
// engine/runtime/dart_vm.cc
DartVM::DartVM(std::shared_ptr<const DartVMData> vm_data,
std::shared_ptr<IsolateNameServer> isolate_name_server)
: settings_(vm_data->GetSettings()),
isolate_name_server_(std::move(isolate_name_server)) {
// 初始化 Dart VM
char* error = Dart_Initialize(¶ms);
// 创建 Isolate
Dart_Isolate isolate = Dart_CreateIsolateGroup(
advisory_script_uri,
advisory_script_entrypoint,
vm_data->GetIsolateSnapshot()->GetDataMapping(),
vm_data->GetIsolateSnapshot()->GetInstructionsMapping(),
nullptr,
nullptr,
&error
);
}
Dart VM 采用 Isolate 模型来实现并发,每个 Isolate 都有自己的内存堆,Isolate 之间通过消息传递通信。主 Isolate 负责运行应用的 UI 代码。
第四步:执行 Dart 代码并构建 Widget 树
Dart VM 启动后,会执行应用的 main() 函数:
void main() {
runApp(MyApp());
}
runApp() 函数是 Flutter 框架的入口点,它的实现如下:
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
这里涉及到 Flutter 框架的几个核心概念:
WidgetsFlutterBinding
WidgetsFlutterBinding 是框架的粘合层,它将框架的各个部分连接在一起:
class WidgetsFlutterBinding extends BindingBase
with GestureBinding,
SchedulerBinding,
ServicesBinding,
PaintingBinding,
SemanticsBinding,
RendererBinding,
WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance!;
}
}
各个 Binding 的职责如下:
- GestureBinding:处理手势事件
- SchedulerBinding:调度帧的绘制
- ServicesBinding:处理平台消息
- PaintingBinding:处理图片缓存
- SemanticsBinding:处理语义化
- RendererBinding:连接渲染树和 Flutter Engine
- WidgetsBinding:连接 Widget 树和渲染树
Widget、Element 和 RenderObject 三棵树
Flutter 采用三棵树的架构来管理 UI:
- Widget 树:描述 UI 的配置信息,是不可变的
- Element 树:Widget 的实例化对象,管理生命周期
- RenderObject 树:负责布局和绘制
当调用 setState() 时,会触发以下流程:
void setState(VoidCallback fn) {
_element!.markNeedsBuild();
}
void markNeedsBuild() {
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
}
void scheduleBuildFor(Element element) {
_dirtyElements.add(element);
element._inDirtyList = true;
}
在下一帧到来时,Framework 会重建脏的 Element:
void buildScope(Element context, [VoidCallback? callback]) {
while (_dirtyElements.isNotEmpty) {
final Element element = _dirtyElements.first;
_dirtyElements.remove(element);
element.rebuild();
}
}
第五步:生成渲染指令并传递给引擎
当 Widget 树构建完成后,Flutter 需要将其转换为实际的绘制指令。这个过程涉及几个关键步骤:
1. 布局(Layout)
RenderObject 树会进行布局计算,确定每个元素的大小和位置:
void performLayout() {
// 计算子节点的约束
final BoxConstraints childConstraints = constraints.loosen();
// 让子节点进行布局
child!.layout(childConstraints, parentUsesSize: true);
// 根据子节点的大小确定自己的大小
size = constraints.constrain(Size(
child!.size.width + padding.horizontal,
child!.size.height + padding.vertical,
));
}
2. 绘制(Paint)
布局完成后,RenderObject 会生成绘制指令:
void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas;
// 创建 Paint 对象
final Paint paint = Paint()
..color = color
..style = PaintingStyle.fill;
// 绘制矩形
canvas.drawRect(offset & size, paint);
// 绘制子节点
context.paintChild(child!, offset);
}
3. 生成 Layer Tree
绘制过程会生成 Layer Tree,这是一个更接近 GPU 的表示:
class PictureLayer extends Layer {
ui.Picture? _picture;
void paint(PaintContext context) {
context.canvas.drawPicture(_picture!);
}
}
4. 生成 Scene
Layer Tree 会被转换为 Scene,这是 Engine 可以理解的格式:
ui.Scene buildScene(ui.SceneBuilder builder) {
layer!.buildScene(builder);
return builder.build();
}
第六步:Engine 渲染流程详解
这是整个流程中最关键的部分,让我们深入了解绘制指令是如何从 Dart 层传递到 Engine,最终渲染到屏幕上的。
1. 绘制指令的格式和传递
Dart 层生成的绘制指令实际上是一系列的 DisplayList 操作。这些操作通过 FFI(Foreign Function Interface)传递给 Engine:
// dart:ui 层
class Canvas {
void drawRect(Rect rect, Paint paint) {
// 将绘制操作编码为 DisplayList 格式
_drawRect(rect.left, rect.top, rect.right, rect.bottom, paint._objects, paint._data);
}
@Native<Void Function(Double, Double, Double, Double, Handle, Handle)>(symbol: 'Canvas_drawRect')
external void _drawRect(double left, double top, double right, double bottom,
List<Object?>? paintObjects, ByteData paintData);
}
在 Engine 端(C++ 层),这些调用会被转换为 DisplayList 操作:
// engine/display_list/display_list_canvas.cc
void DisplayListCanvas::drawRect(const SkRect& rect, const DlPaint& paint) {
// 创建 DisplayList 操作
builder_->DrawRect(rect, paint);
}
// DisplayListBuilder 记录操作
void DisplayListBuilder::DrawRect(const SkRect& rect, const DlPaint& paint) {
// 将操作添加到 DisplayList
Push<DrawRectOp>(0, rect, paint);
}
2. DisplayList 的结构
DisplayList 是一个高效的绘制指令缓冲区,它将所有绘制操作序列化存储:
class DisplayList {
// 操作码枚举
enum OpType {
kDrawRect,
kDrawCircle,
kDrawPath,
kDrawImage,
// ... 更多操作
};
// 存储序列化的绘制操作
std::vector<uint8_t> ops_;
// 存储引用的对象(如图片、着色器等)
std::vector<sk_sp<SkImage>> images_;
std::vector<sk_sp<SkShader>> shaders_;
};
3. 从 UI Thread 到 Raster Thread
Scene 构建完成后,需要从 UI Thread 传递到 Raster Thread 进行光栅化:
// engine/shell/common/animator.cc
void Animator::Render(std::unique_ptr<flutter::LayerTree> layer_tree) {
// 将 LayerTree 提交给 Raster Thread
delegate_.OnAnimatorDraw(layer_tree_pipeline_->Produce());
}
// engine/shell/common/rasterizer.cc
void Rasterizer::Draw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline) {
// 在 Raster Thread 上执行
PipelineConsumeResult consume_result = pipeline->Consume([&](LayerTree& layer_tree) {
RasterStatus status = DoDraw(std::move(layer_tree));
});
}
4. 光栅化过程
在 Raster Thread 上,LayerTree 被遍历并转换为 GPU 指令:
RasterStatus Rasterizer::DoDraw(std::unique_ptr<LayerTree> layer_tree) {
// 创建 Skia Canvas
SkCanvas* canvas = surface_->getCanvas();
// 创建 Frame
std::unique_ptr<Frame> frame = std::make_unique<Frame>(
surface_->makeImageSnapshot(),
framebuffer_info
);
// 遍历 Layer Tree 并绘制
if (layer_tree) {
// PrerollContext 用于准备阶段
PrerollContext preroll_context = {
.raster_cache = raster_cache_.get(),
.gr_context = surface_->getGrContext(),
.view_embedder = view_embedder_,
.mutators_stack = mutators_stack,
.dst_color_space = dst_color_space,
.cull_rect = cull_rect,
.surface_needs_readback = surface_needs_readback,
};
layer_tree->Preroll(preroll_context);
// PaintContext 用于实际绘制
PaintContext paint_context = {
.internal_nodes_canvas = internal_nodes_canvas,
.leaf_nodes_canvas = leaf_nodes_canvas,
.gr_context = surface_->getGrContext(),
.view_embedder = view_embedder_,
.raster_cache = raster_cache_.get(),
};
layer_tree->Paint(paint_context);
}
// 提交给 GPU
frame->Submit();
}
5. DisplayList 的执行
当一个 PictureLayer 被绘制时,其 DisplayList 会被执行:
void PictureLayer::Paint(PaintContext& context) const {
// 获取 DisplayList
sk_sp<DisplayList> display_list = picture_->display_list();
// 创建 DisplayListCanvas
DisplayListCanvas canvas(context.canvas);
// 执行 DisplayList 中的所有操作
display_list->Dispatch(canvas);
}
void DisplayList::Dispatch(Dispatcher& dispatcher) const {
// 遍历所有操作
const uint8_t* ptr = ops_.data();
const uint8_t* end = ptr + ops_.size();
while (ptr < end) {
OpType op = static_cast<OpType>(*ptr++);
switch (op) {
case kDrawRect: {
SkRect rect = *reinterpret_cast<const SkRect*>(ptr);
ptr += sizeof(SkRect);
DlPaint paint = *reinterpret_cast<const DlPaint*>(ptr);
ptr += sizeof(DlPaint);
dispatcher.drawRect(rect, paint);
break;
}
// ... 处理其他操作
}
}
}
6. Skia 渲染
最终,所有的绘制操作都会通过 Skia 图形库转换为 GPU 指令:
void SkCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
// 应用当前的变换矩阵
SkRect transformed_rect;
this->getTotalMatrix().mapRect(&transformed_rect, rect);
// 创建 GPU 绘制操作
if (gpu_backed_) {
GrRenderTargetContext* rtc = this->internal_private_accessTopLayerRenderTargetContext();
// 生成 GPU 绘制指令
rtc->drawRect(nullptr, std::move(paint), GrAA::kYes,
this->getTotalMatrix(), transformed_rect);
}
}
7. GPU 指令的提交
Skia 会将绘制指令批量化并提交给 GPU:
void GrRenderTargetContext::drawRect(const GrClip* clip,
GrPaint&& paint,
GrAA aa,
const SkMatrix& viewMatrix,
const SkRect& rect) {
// 创建绘制操作
std::unique_ptr<GrDrawOp> op = GrRectOpFactory::Make(
fContext, std::move(paint), viewMatrix, rect, aa
);
// 添加到操作列表
this->addDrawOp(clip, std::move(op));
}
void GrRenderTargetContext::flush() {
// 将所有操作提交给 GPU
GrOpsTask* opsTask = this->getOpsTask();
opsTask->execute(flushState);
// 实际的 GPU 命令提交(OpenGL/Metal/Vulkan)
gpu->submitCommandBuffer();
}
平台通道(Platform Channel)机制
Flutter 与原生平台的通信通过 Platform Channel 实现,这是一个双向通信机制:
Dart 端发送消息
class MethodChannel {
Future<T?> invokeMethod<T>(String method, [dynamic arguments]) async {
final ByteData? result = await binaryMessenger.send(
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
return codec.decodeEnvelope(result) as T?;
}
}
消息编码和传输
消息通过 StandardMessageCodec 编码为二进制格式:
class StandardMessageCodec implements MessageCodec<Object?> {
ByteData? encodeMessage(Object? message) {
if (message == null)
return null;
final WriteBuffer buffer = WriteBuffer();
writeValue(buffer, message);
return buffer.done();
}
void writeValue(WriteBuffer buffer, Object? value) {
if (value == null) {
buffer.putUint8(_valueNull);
} else if (value is bool) {
buffer.putUint8(value ? _valueTrue : _valueFalse);
} else if (value is int) {
buffer.putUint8(_valueInt32);
buffer.putInt32(value);
}
// ... 处理其他类型
}
}
Engine 层转发
Engine 接收到消息后,会将其转发给对应的平台代码:
// engine/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
NSString* method = call.method;
id arguments = call.arguments;
// 分发到对应的处理器
if ([method isEqualToString:@"SystemNavigator.pop"]) {
[self popSystemNavigator];
result(nil);
}
// ... 处理其他方法
}
性能优化机制
Flutter 采用了多种机制来优化性能:
1. Layer 缓存(RasterCache)
对于复杂但不经常变化的 Layer,Flutter 会将其光栅化结果缓存起来:
class RasterCache {
struct Entry {
sk_sp<SkImage> image;
size_t access_count;
size_t memory_usage;
};
std::unordered_map<uint64_t, Entry> cache_;
bool Prepare(Layer* layer, const SkMatrix& ctm) {
// 判断是否应该缓存
if (layer->paint_bounds().width() * layer->paint_bounds().height() > threshold_) {
// 光栅化 Layer
sk_sp<SkImage> image = RasterizeLayer(layer, ctm);
cache_[layer->unique_id()] = {image, 0, image->size()};
return true;
}
return false;
}
};
2. 脏区域优化
Flutter 只会重绘发生变化的区域:
void markNeedsPaint() {
if (_needsPaint)
return;
_needsPaint = true;
// 标记脏区域
if (isRepaintBoundary) {
// 如果是重绘边界,只需要重绘自己
if (owner != null) {
owner!._nodesNeedingPaint.add(this);
owner!.requestVisualUpdate();
}
} else if (parent is RenderObject) {
// 否则向上传递
final RenderObject parent = this.parent! as RenderObject;
parent.markNeedsPaint();
}
}
3. Widget 树 Diff 算法
Flutter 使用高效的 Diff 算法来更新 Widget 树:
void update(Widget newWidget) {
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(widget != null);
assert(newWidget != null);
assert(newWidget != widget);
assert(depth != null);
assert(Widget.canUpdate(widget, newWidget));
_widget = newWidget;
}
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
热重载(Hot Reload)机制
Flutter 的热重载是开发效率的关键特性,其原理如下:
1. 监听文件变化
开发工具监听源代码文件的变化:
final FileSystemWatcher watcher = FileSystemWatcher(
projectDirectory,
recursive: true,
);
watcher.events.listen((WatchEvent event) {
if (event.type == ChangeType.MODIFY) {
// 触发热重载
_performHotReload();
}
});
2. 增量编译
只编译发生变化的文件:
Future<CompilerOutput> _compile() async {
final CompilerOutput output = await generator.recompile(
mainUri,
invalidatedFiles,
outputPath: dillOutputPath,
packageConfig: packageConfig,
);
return output;
}
3. 更新 Dart VM
将新的代码注入到运行中的 VM:
// engine/runtime/dart_isolate.cc
bool DartIsolate::ReloadSources(const std::string& kernel_buffer) {
// 暂停 Isolate
Dart_EnterIsolate(isolate());
Dart_EnterScope();
// 加载新的 kernel
Dart_Handle result = Dart_LoadLibraryFromKernel(
kernel_buffer.data(),
kernel_buffer.size()
);
// 触发重建
if (!Dart_IsError(result)) {
// 调用 Dart 层的 reassemble 方法
Dart_Handle reassemble = Dart_Invoke(
Dart_RootLibrary(),
Dart_NewStringFromCString("_reassemble"),
0,
nullptr
);
}
Dart_ExitScope();
Dart_ExitIsolate();
return !Dart_IsError(result);
}
4. 重建 Widget 树
Dart 层接收到热重载信号后,会重建整个 Widget 树:
void reassemble() {
_buildOwner!.reassemble(_renderViewElement!);
}
void reassemble(Element root) {
root._reassemble();
root.visitChildren((Element child) {
reassemble(child);
});
}
总结
通过深入剖析 Flutter 的工作原理,我们可以看到:
- Flutter 的启动 是一个多阶段的过程,从原生应用启动,到 Engine 初始化,再到 Dart 代码执行
- 三棵树架构 使得 Flutter 能够高效地管理 UI 状态和渲染
- 绘制指令通过 DisplayList 格式从 Dart 层传递到 Engine 层
- 多线程架构 确保了 UI 的流畅性,UI Thread 负责构建,Raster Thread 负责渲染
- Skia 图形库 最终将绘制指令转换为 GPU 命令
- 各种优化机制 如 RasterCache、脏区域优化等,保证了高性能
- 热重载机制 通过增量编译和 VM 更新,实现了高效的开发体验
理解这些底层机制,不仅能帮助我们更好地使用 Flutter,还能在遇到性能问题时知道如何优化。Flutter 的设计理念和实现方式,也为我们设计自己的框架提供了宝贵的参考。

浙公网安备 33010602011771号