Flutter Engine 的启动简析
起因于自己想研究一下 game-engine,但是主流的 ue 什么的太过庞大了(庞大也是必然),于是想到了比较亲切的 Flutter Engine(其实也蛮复杂的),于是想梳理一下其 启动
和 绘制
的大概流程。
开始
注意, Flutter Engine 也相当庞大,由于精力有限,只能梳理部分内容。
在开始之前,列举一下相关的代码文件夹
-
fml (基础功能的封装,比如基于task 的线程,log,时钟,路径,哈希等 很底层很硬核的东西)
-
shell/common (直接引用 shell.h 注释的一句话:Perhaps
the single most important class
in the Flutter engine repository.) -
shell/runtime (不同平台对
embedder engine
具体实现) -
impeller (图形后端,也就是roadmap 提到的在重构东西,你会发现它的 commit 时间最近,还带了好多个⚠️)
启动
Flutter 有几个线程? 首先排除单线程,再排除经常看到的 5 线程模型(其实没啥问题)。 为什么皮这一下,因为实际情况可能比你想象的复杂的多,举两个例子:你在 Dart 开了一个 Compute Isolate
,并且愉快的使用 fronted-server
提供的 hot-reload,这下情况就复杂了吧。
言归正传,我们从官方的 examples/vulkan_glfw 例子进行分析(我接触的时候当时只有一个 glfw-opengl 的例子,令人感概)
如何启动 Fluuter Engine ?
#include "embedder.h" // Flutter's Embedder ABI.
...
FlutterRendererConfig config = {};
FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, ...);
...
代码是简洁的,但 实现是复杂的!
这里提一句,启动 engine 还需要你的 dart_project_path
(就你写dart 代码的那个目录) 和 icudtl.dat
(flutter engine 编译后的产物,一年前我的 Ryzen 3600 要编译半小时,不知道现在如何)这两个参数,还有一个可选的 snapshot
(提高重新 run & debug 的构建速度)
提一嘴,印象中应该是任何平台的 engine 启动函数定义,都是基于 embedder.h
。
接下来,让我们慢慢看看 FlutterEngineRun
函数做了什么 (实现在此 embedder.cc)
Run 很重要,作用就是调用了后面这两位重量级函数,可以说没有那两行代码的调用,Flutter 就无法运行,所以我们就不鸟他了。
FlutterEngineInitialize
这个函数,创建一堆我们需要的资源,但是很多都没有立即使用(启动交给 FlutterEngineRunIntialized
)
因为东西太多了,比如 commandline,aot 资源加载, 供 root_isolate, PlatformViewEmbedder ... 的 回调的各种函数创建等等,这里挑个大伙最想知道的:
真正
的 flutter engine 创建

他的参数最有趣的 就属 thread_host
和 task_runners
了
task_runners 基于任务的线程模型在此,感兴趣的可以去看 effective c++,这里你只需要可以下发任务给这个线程工作就行。
thread_host 我的建议是点进去看 那个很长的 create 函数,看了就明白了。

这个 thread_host 的创建走同文件内的静态函数 CreateEngineManagedThreadHost
或 CreateEmbedderManagedThreadHost
,做了什么事情呢?一张截图就够了。

FlutterEngineRunIntialized
数据都准备好了,那么现在就要开始 启动了!! 启动的步骤分为三步,具体看下方:

步骤2/3 具体实现还没看,不过基本可以确定自己的屎山 dart 代码 在最后一个 RunRiitIsolate
被跑起来了。 我们感兴趣的还是开头官方注释的那句话, 这个 Shell
可能就是 Flutter Engine 最重要的部分。
LaunchShell 也很重要,因为它调用了后面的 Create 函数,所以不看了。
-
Shell::Create()
负责启动DartVM
和isolate
如果提供了snapshot
就从中启动。(我猜没有提供快照,应该会生成一份供下次使用) -
Shell::CreateWithSnapshot()
主要负责run 一下面这个CreateShellOnPlatformThread
作为他的 task -
Shell::CreateShellOnPlatformThread
就比较重量级了,同样也是创建资源, 比如raster
线程需要的Rasterizer
对象,还有诸如io_manager
,platformview
或者ui-thread
的animator
等等。这里可以看到很多 std 提供的 future 和 promise的东西,不知道是不是和 js|dart 那一套类似,总之就是异步操作,只能说亲切可爱了, -
Shell::Setup
我截个参数图,你也清楚了
小结
其实源代码比上面讲的多太多了, 虽说 连组件都写不明白的 还要操心 架构 的事情有点🤭,不过总该得研究一下。 感觉最有趣的就是🤔,基于 任务的发送给对应 线程的思维 和 从线程出发 编写任务的方式 有很大的不同。
绘制
绘制实际上是很复杂的,而且图形后端还在重构,详细的还得看 Skia
和 ANGLE
,shader 使用的语言还是自家的 sksl
,还有各种 Delegate
。再比如官方的 Vulkan Example
内,处理同步也是简单的 vkQueueWaitIdle
。
反正很复杂(懒得看),所以,我拎出连个比较抽象简单的类瞅瞅。
- Rasterizer 类 (Raster 线程持有)
Shell::CreateShellOnPlatformThread
创建了这个对象,这里列出大家都熟悉的函数。
RasterStatus Draw(std::shared_ptr<LayerTreePipeline> pipeline,
LayerTreeDiscardCallback discard_callback = NoDiscard);
void DrawLastLayerTree(std::unique_ptr<FrameTimingsRecorder>frame_timings_recorder);
对于 LayerTree
的内容,参阅 flutter/flow/layers/layer_tree.h, 有趣的是 Rasterizer
还有屏幕截图相关的代码,也是对 LayerTree 进行操作
-
Animator (UI 线程持有)
平台层会有一个
vsync_waiter
,Animator
通过传来的这个信号,开始他的逻辑。(我也不知道这个vsync具体是怎么实现的,是类似 G-Sync 那种显示设备通知 GPU 还是什么的,还有手机那种 LTPO 技术的底层原理,反正咱也是不懂)Animator
做了什么? 如果你了解 Flutter Dart端 的ScheduleFrame
,下面的代码你也猜出来什么意思了。通过某个Delegate
,搞个回调函数。接下来是猜想部分,反正是通过某一个Dispatcher
把需要逻辑更新的指令下发给 Dart 端就对了。
void RequestFrame(bool regenerate_layer_tree = true);
void BeginFrame(std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder);
bool CanReuseLastLayerTree();
void DrawLastLayerTree(std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder);
如果你操刀过某些图形API的代码,一般会有一个render_loop,但是我没找到engine 的render_loop在哪,但是想了一想,其实感觉就是 example 里面的那个 glfwShouldCloseWindow
,也就是你开发者自己管理的,还有平台指针事件下发,也是通过一个static 函数调用一次 SendEvent
塞给 engine,这就是抽象的魅力。这也应该是 Flutter 更适合作为一个 寄生🐛,跑到某个宿主环境上原因之一吧。
总结
看这个有用吗?有用,自己的文章数量会上升。
为什么看这个?看到dart 各种 external 方法,心里很痒想知道这些到底在干什么,现在舒服了😌。
Flutter
还值得学吗,有必要看这么深吗? 我的评价是自己做取舍
,没有一劳永逸,YYDS 的东西,但是有长期不变的设计思维
, 经典算法
和 底层知识
,这些才是阅读源代码获得最有价值的东西!
转载自:https://juejin.cn/post/7135653677066354702