Flutter 线程模型:三种事件的线程流转与调度
目录
一、三种事件的线程流转对比
三种事件(VSync、UI交互、PlatformChannel)的完整链路都涉及两次线程跨越:从 Platform Thread 到 UI Thread(事件注入),再通过 Pipeline 到 Raster Thread(渲染输出)。但它们的触发源、路径形状、响应方各不相同。
1.1 VSync 事件
触发源:显示器硬件信号。
通路形状:硬件 → VsyncWaiter → UI Thread → Raster Thread
显示器 VSync 信号
│
▼
VsyncWaiter (平台适配层) ← 到达线程取决于平台实现
│ (Android: Platform Thread via Choreographer
│ iOS: CADisplayLink 所在线程
│ Desktop: VSync 等待线程
│ OHOS: 独立回调线程)
│ FireCallback() 内部
│ PostTask(UITaskRunner, Animator::BeginFrame)
▼
┌──────────────────────────────┐
│ UI Thread │
│ │
│ Animator::BeginFrame() │
│ → Engine::BeginFrame() │
│ → Dart: window.onBeginFrame│
│ → WidgetsBinding.drawFrame │
│ → build / layout / paint │
│ → LayerTree 生成 │
│ │
│ Pipeline::Produce(LayerTree) │ ← 提交到渲染管线
└──────────┬───────────────────┘
│ Consume()
▼
┌──────────────────────────────┐
│ Raster Thread │
│ │
│ Rasterizer::Draw() │
│ → Skia/Impeller 光栅化 │
│ → Surface::Flush() │
│ → SwapBuffers → GPU │
└──────────────────────────────┘
关键特征:
- 唯一一个由硬件周期性驱动的事件
- 唯一一个必然触发 Raster Thread 的事件(因为要渲染帧)
- VSync 本身不携带数据,只携带时间戳(
frame_start_time和frame_target_time)
1.2 UI 交互事件(触摸/鼠标/键盘)
触发源:用户输入(触摸/鼠标/键盘/滚轮)。
通路形状:硬件 → Platform 层 → UI Thread →(可选)Raster Thread
用户触摸/点击
│
▼
Platform 层面捕获输入事件 ← Platform Thread 或输入线程
│
│ 创建 PointerDataPacket
│ DispatchPointerDataPacket()
│ 内部 PostTask(UITaskRunner, Engine::DispatchPointerDataPacket)
▼
┌──────────────────────────────┐
│ UI Thread │
│ │
│ Engine::DispatchPointer... │
│ → Dart: window.onPointer...│
│ → GestureBinding │
│ → HitTest │
│ → GestureRecognizer │
│ → setState / 动画启动 │
│ → Animator::Request... │ ← 如需渲染,请求下一帧
│ │
│ Animator 等到下一个 VSync │ ← 不走 Pipeline,走 VSync
└──────────────────────────────┘
关键特征:
- 事件携带具体数据(坐标、按键码、时间戳等)
- 不直接触发渲染——触摸事件本身只会更新状态,是否需要渲染由 Dart 层的
setState/AnimationController决定 - 如果需要渲染(
Animator::RequestFrame被调用),则等到下一个 VSync 才会走渲染管线
1.3 PlatformChannel 消息
触发源:Dart 代码 MethodChannel.invokeMethod() 或平台侧主动发送。
通路形状:Dart UI Thread ↔ Platform Thread(双向,且通常需要来回)
Dart → Platform 方向:
[Dart UI Thread]
MethodChannel.invokeMethod('getBattery')
│
▼ C++ 边界 (Dart → C++)
PlatformView::HandlePlatformMessage()
│
│ PostTask(PlatformTaskRunner, 平台处理回调)
▼
[Platform Thread]
平台 SDK 调用(如获取电池、读取剪贴板)
│
│ 结果准备就绪
│ PostTask(UITaskRunner, 结果反序列化回调)
▼
[Dart UI Thread]
PlatformMessageResponse 回调
→ Future.then / async callback
Platform → Dart 方向:
[Platform Thread]
平台主动发送消息(如应用生命周期变化、剪贴板变更)
│
│ PostTask(UITaskRunner, HandlePlatformMessage)
▼
[Dart UI Thread]
Engine::HandlePlatformMessage()
→ Dart: window.onPlatformMessage
→ BinaryMessenger handler
关键特征:
- 双向:Dart→Platform 和 Platform→Dart 各一次跨线程投递
- 至少两次线程切换(去程和回程)
- 不直接触发渲染——除非 Dart 侧
setState - PlatformChannel 是 Flutter 中最常见的跨线程延迟来源
1.4 三者异同总结表
| 维度 | VSync | UI 交互事件 | PlatformChannel 消息 |
|---|---|---|---|
| 触发源 | 显示器硬件信号 | 用户输入(触摸/鼠标/键盘) | Dart 代码或平台代码 |
| 触发频率 | 周期性(60/90/120 Hz) | 事件驱动(随机) | 调用驱动(随机) |
| 到达 Platform Thread | 不一定(Android 是,iOS/Linux/OHOS 不是) | 是(输入事件走平台主线程) | Dart→Platform 方向是(要执行平台 API) |
| 入 UI Thread 方式 | PostTask(UIRunner, BeginFrame) |
PostTask(UIRunner, DispatchPointer) |
PostTask(UIRunner, HandleMessage) |
| 是否携带数据 | 仅时间戳 | 坐标/按键/压力/时间戳 | 任意二进制数据 |
| 是否必然触发渲染 | 是(VSync 即渲染) | 否(取决于 Dart handler) | 否(取决于 Dart handler) |
| 是否经过 Raster Thread | 是(固定 Pipeline) | 仅当触发了 RequestFrame |
仅当触发了 RequestFrame |
| 延迟敏感度 | 极高(掉帧即 Jank) | 高(触摸延迟 > 16ms 感知卡顿) | 一般(100ms 内可接受) |
| 是否可批量合并 | 否(一帧一帧) | 可批量(同一帧内多个事件) | 否(每个消息独立) |
二、Dart UI 线程内的事件队列与优先级
2.1 三类事件都在同一个队列里吗?
是,都在 UI TaskRunner 的同一个 TaskQueue 中。
Flutter Engine 为 UI Thread 创建了一个 MessageLoop,它关联一个 TaskQueue。所有需要 UI Thread 执行的任务——无论源于 VSync、触摸还是 PlatformChannel——最终都以 C++ fml::closure 的形式投递到这个队列。
UI Thread 的 TaskQueue
┌─────────────────────────────────────┐
│ [VSync] Animator::BeginFrame │
│ [Touch] Engine::DispatchPointer... │
│ [Msg] Engine::HandlePlatformMsg │
│ [VSync] Animator::BeginFrame │
│ [Msg] PlatformMessageResponse │
│ [Touch] Engine::DispatchPointer... │
│ ... │
└─────────────────────────────────────┘
每个任务会在 UI Thread 的 MessageLoop 中被 RunExpiredTasksNow() 逐一取出并执行。
但这不是一个"先入先出"的简单队列——它引入了优先级和暂停机制。
2.2 TaskSourceGrade 优先级系统
MessageLoopTaskQueues 支持三种任务等级:
TaskSourceGrade::kUserInteraction <!-- 最高优先级 -->
用途:VSync 回调、触摸事件分发
特征:不会被暂停
TaskSourceGrade::kDartEventLoop <!-- 中间优先级 -->
用途:Dart 的 EventLoop 推进(Timer/Future/microtask)
特征:可以被暂停(帧期间暂停)
TaskSourceGrade::kUnspecified <!-- 默认优先级 -->
用途:普通 C++ 任务、图片加载完成回调
特征:无特殊处理
实际执行优先级:kUserInteraction > kDartEventLoop > kUnspecified
当 UI TaskRunner 从 TaskQueue 中取出任务时,总是优先取 kUserInteraction 等级的任务,即使后者入队更晚。
2.3 Dart EventLoop 与 C++ TaskQueue 的关系
这是一个关键的理解点。
Dart 的 EventLoop(Microtask Queue + Event Queue)不是独立运行的。 它被嵌入在 C++ 层的 UI TaskRunner 中。
每次 Dart EventLoop 推进一个"tick",都是因为一个 C++ Task 调用了 Dart 的 C API:
UI TaskRunner 取下一个 Task
│
├── 类型 A:C++ 原生任务
│ ├── Animator::BeginFrame()
│ │ └── 内部调用了 Dart: window.onBeginFrame()
│ │ └── Dart 在这个调用期间处理自己的 Microtask/Event
│ │
│ ├── Engine::DispatchPointerDataPacket()
│ │ └── 内部调用了 Dart: window.onPointerDataPacket()
│ │
│ └── Engine::HandlePlatformMessage()
│ └── 内部调用了 Dart: window.onPlatformMessage()
│
└── 类型 B:Dart EventLoop 推进任务
└── DrainMicrotaskQueue() + HandleEvents()
└── 这是 Dart C API 的周期性调用
这意味着 Dart 侧的 Microtask Queue 和 Event Queue 是在 C++ 任务执行过程中被同步处理的。Dart 没有自己的"线程"——它依附在 UI Thread 上进行时间片调度。
2.4 帧期间的暂停机制
当 VSync 到来,UI Thread 开始执行 Animator::BeginFrame 时,Flutter Engine 会调用 PauseDartEventLoopTasks():
VSync 到达 → FireCallback() → 入队 BeginFrame 任务
│
│ PauseDartEventLoopTasks()
│ → 将 UI TaskQueue 中的 "kDartEventLoop" 级别任务暂停
│
▼
UI Thread 执行 BeginFrame()
│ build / layout / paint 期间:
│ - kUserInteraction 任务(触摸)可以打断并立即执行
│ - kDartEventLoop 任务(Timer/Future)被延迟到帧结束后
│
▼
BeginFrame 完成 → ResumeDartEventLoopTasks()
→ 恢复 kDartEventLoop 任务执行
暂停的原因:如果 Dart EventLoop 在帧中间被 Timer 或 Future 回调打断,会破坏帧的原子性,可能导致一帧内 build/layout/paint 不完整。
2.5 事件先后顺序全景图
在 Dart UI Thread 上,一个典型的时间片是这样的:
时间 →
├── VSync 到达 ────────────────┤ (kUserInteraction)
│ BeginFrame() │
│ Dart: onBeginFrame │
│ WidgetsBinding.drawFrame│
│ build / layout / paint│
│ │ ← 此时 DartEventLoop 任务被暂停
│ ← 其间如果来了触摸事件 │ (kUserInteraction, 可打断)
│ DispatchPointerDataPacket│
│ Dart: onPointerDataPacket│
│ GestureBinding 处理 │
│ │
├── VSync 处理完成 ────────────┤
│ ResumeDartEventLoopTasks() │
│ │
├── 处理被延迟的 Dart 任务 ────┤ (kDartEventLoop)
│ Timer 回调 │
│ Future.then() │
│ scheduleMicrotask() │
│ │
├── 处理普通 C++ 任务 ─────────┤ (kUnspecified)
│ 图片加载完成回调 │
│ 其他异步资源 │
极端情况:如果 Dart 的 build() 执行了太久(超过 16ms),VSync 的帧预算已经耗尽,下一帧的 VSync 回调已经入队。这时高优先级的下一帧 VSync 会打断当前任务——这就是掉帧/Jank 的根源。
注:触摸事件和 VSync 并不一定使用
kUserInteraction优先级——Engine 中各任务的实际TaskSourceGrade取决于投递时的选择。但出于简化理解,本文将它们归入高优先级类别。
三、Host UI Thread 与 Dart UI Thread 的双向触发
3.1 外部 → Dart:单向投递
Platform Thread 或平台回调线程向 Dart UI Thread 投递事件,使用统一的模式:
触摸/Platform 消息(从 Platform Thread 出发):
[Platform Thread]
事件发生(触摸输入/平台主动消息)
│
│ 创建 Flutter 内部数据结构
│ (PointerDataPacket / PlatformMessage)
│
▼
fml::TaskRunner::RunNowOrPostTask(ui_runner, callback)
│
├── 如果当前线程 == UI Thread → 直接执行
│
└── 如果当前线程 ≠ UI Thread → PostTask 到 UI TaskQueue
VSync(从平台特定的 VSync 线程出发):
[VSync 回调线程] ← 不一定是 Platform Thread
(Android Platform Thread、iOS CADisplayLink 线程、
Desktop VSync wait 线程、OHOS 独立回调线程)
│
▼
fml::TaskRunner::RunNowOrPostTask(ui_runner, Animator::BeginFrame)
│
└── 几乎总是 ≠ UI Thread → PostTask 到 UI TaskQueue
要点:宿主线程和 VSync 回调线程都没有权限直接调用 Dart 函数。任何对 Dart 侧的通信都必须经过 TaskRunner::PostTask 这个抽象。
3.2 Dart → 宿主:通过 C++ 中转
Dart 代码不能直接调用平台 API。当 Dart 需要与平台交互时(如 MethodChannel.invokeMethod),路径是:
[Dart UI Thread]
MethodChannel.invokeMethod('name', args)
│
▼ C++ 边界(Dart C API → C++)
PlatformView::HandlePlatformMessage(message)
│
│ 注意:这里不是在 UI Thread 上直接调用平台 API
│ 而是将消息投递到 Platform TaskRunner
│
▼ PostTask(platform_runner, 平台处理)
[Platform Thread]
平台 SDK 处理(文件 I/O、传感器、系统 API)
│
▼ PostTask(ui_runner, 结果)
[Dart UI Thread]
PlatformMessageResponse 回调
关键设计原则:Dart(UI Thread)绝不直接调用平台 API。所有平台交互都投递到 Platform Thread 去执行。这样即使平台 API 耗时(如文件读写、网络请求),也不会阻塞 UI Thread 的帧渲染。
3.3 核心机制:RunNowOrPostTask
TaskRunner::RunNowOrPostTask 是跨线程通信的核心工具:
RunNowOrPostTask(目标_runner, 任务)
│
├── RunsTasksOnCurrentThread() == true
│ → 直接执行(零开销)
│
└── RunsTasksOnCurrentThread() == false
→ runner->PostTask(task) ← 投递到目标线程的 MessageLoop
这个函数在 Flutter Engine 中大量使用,因为它自动优化了同线程调用的情况(例如某些平台 VSync 直接在当前 UI Thread 上触发)。
3.4 同步等待与死锁风险
有时 UI Thread 需要等待 Platform/Raster Thread 完成某个操作后才能继续。Flutter 使用 AutoResetWaitableEvent 来实现同步:
[UI Thread]
task_runners_.GetRasterTaskRunner()->PostTask([&latch]() {
// Raster Thread 上执行
doSomething();
latch.Signal(); // 通知 UI Thread
});
latch.Wait(); // UI Thread 阻塞等待 ← 风险点
这种同步等待是死锁高风险区域,必须满足两个条件才能安全使用:
- 目标线程不是当前线程(否则死锁)
- 目标线程当前的执行流不会等待 UI Thread(否则循环等待)
Flutter Engine 在以下场景使用同步等待:
- 纹理注册/注销时需要等待 Raster Thread 完成
- Shell 销毁时需要等待所有线程退出
- Surface 创建时需要等待 Raster 上下文就绪
四、Flutter 线程与平台线程的适配层
4.1 适配层架构
Flutter 的线程模型不直接依赖任何操作系统的线程 API。它通过一层抽象的适配接口来实现跨平台:
┌─────────────────────────────────────────────────┐
│ Flutter Engine 核心(平台无关) │
│ │
│ Shell / Engine / Animator / Rasterizer │
│ fml::MessageLoop / fml::TaskRunner │
│ fml::Thread(pthread 封装) │
│ │
│ 只知道:TaskRunner → PostTask/RunNowOrPostTask │
│ 不知道:具体怎么"唤醒"一个线程 │
└──────────────────────┬──────────────────────────┘
│
┌─────────────┬─────────────┬──────────────┐
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐
│ MessageLoop │ │MessageLoop│ │MessageLoop│ │ MessageLoop │
│ Android │ │ iOS │ │ Linux │ │ **OHOS** │
│ │ │ │ │ │ │ │
│ Looper │ │CFRunLoop │ │ epoll+ │ │ **libuv** │
│ + pipe/ │ │ + Source │ │ timerfd │ │ **+ timerfd** │
│ eventfd │ │ │ │ │ │ **+ epoll** │
└──────────────┘ └──────────┘ └──────────┘ └──────────────┘
适配层的职责很窄:
| 适配接口 | 职责 |
|---|---|
MessageLoopImpl::Create() |
创建平台特定的 MessageLoop 实现 |
Wakeable::WakeUp(TimePoint) |
唤醒线程(在指定时间) |
MessageLoopImpl::Run() |
进入事件循环 |
MessageLoopImpl::Terminate() |
终止事件循环 |
Engine 核心代码只通过这四项抽象方法与平台交互(其中 WakeUp 继承自 Wakeable 接口)。这意味着:
- Engine 本身不知道 Android 的
Looper、iOS 的CFRunLoop、Linux 的epoll、OHOS 的libuv的存在 - 新增一个平台只需要实现
MessageLoopImpl的子类(约 150 行代码)
4.2 跨平台替换策略
对于 UI / Raster / IO 三个 Engine 创建的线程,适配是透明的:
fml::Thread 构造函数
│
├── pthread_create() ← 创建系统线程
│
├── setter(config) ← 设置线程名和优先级(平台定制)
│
├── MessageLoop::EnsureInit ← 创建 MessageLoop
│ └── MessageLoopImpl::Create() ← 平台分发
│ ├── Android → MessageLoopAndroid
│ ├── iOS → MessageLoopDarwin
│ ├── Linux → MessageLoopLinux
│ └── **OHOS** → **MessageLoopOhos**
│
└── loop.Run() ← 进入事件循环
对于 Platform Thread(宿主线程),适配方式不同:
宿主线程初始化 Flutter Engine 时:
│
│ 向 Engine 传入"宿主线程的 TaskRunner"
│ (不一定是 MessageLoop,只要是 TaskRunner 接口即可)
│
│ 内部实现方式因平台而异:
│ ├── Android:在 Java Looper 线程上注册 TaskRunner
│ ├── iOS:在 CFRunLoop 上注册 TaskRunner
│ ├── **OHOS:在 libuv loop 上注册 TaskRunner**
│ └── 嵌入器:通过 Embedder API 提供 platform TaskRunner
│
▼
Engine 统一通过 TaskRunner::PostTask 向宿主线程投递任务
核心抽象总结:
| 抽象层 | 职责 | 平台无关 | 平台相关 |
|---|---|---|---|
fml::Thread |
创建线程 + 初始化 MessageLoop | ✅ | 线程名/优先级设置 |
MessageLoopImpl |
事件循环(等待、唤醒) | ❌ 基类 | ✅ 子类实现 |
Wakeable::WakeUp |
唤醒等待中的线程 | ❌ 接口 | ✅ 具体唤醒方式 |
TaskRunner::PostTask |
向指定线程投递任务 | ✅ | 底层发信号方式 |
VsyncWaiter |
等待垂直同步信号 | ❌ 基类 | ✅ 平台 VSync API |
五、其他事件一览
前三章重点分析了 VSync、UI 交互、PlatformChannel 三类"高频核心事件"。但在 Flutter Engine 的完整运行时中,UI/Raster/IO/Platform 四线程还要处理大量其他事件。这些事件虽然不被普通开发者直接感知,但同样是线程调度的重要组成部分。
5.1 UI Thread 的其他事件
UI Thread 除了处理 VSync/触摸/PlatformChannel 三类核心事件外,还处理以下事件。它们全部通过 PostTask(UITaskRunner, ...) 进入同一个 TaskQueue,遵循 §2 描述的优先级规则。
| 事件 | 来源 | 触发条件 | 对应 Dart 回调 |
|---|---|---|---|
| Semantics 更新 | Dart 帧内部自动触发 | 每帧 updateSemantics() |
window.onSemanticsEnabled |
| 窗口指标变化 | Platform Thread → SetViewportMetrics() |
屏幕旋转、键盘弹出、窗口 resize | window.onMetricsChanged |
| 图片解码完成 | IO Thread 解码完成 → PostTask | 网络/文件图片下载后 | ImageStreamListener.onImage |
| 外部纹理可用 | Raster Thread 或 Platform Thread → MarkTextureFrameAvailable |
视频帧到达、Camera 帧到达 | 框架层自动处理 |
| 生命周期状态 | Platform Thread → NotifyLifecycleState() |
应用前后台切换 | WidgetsBindingObserver.didChangeAppLifecycleState |
| 系统语言/区域 | Platform Thread → ComputePlatformResolvedLocales() |
系统设置变更 | onLocalesChanged |
| Accessibility 主动查询 | 无障碍服务 → Platform Thread → PostTask | TalkBack/屏幕阅读器操作 | SemanticsNode 更新 |
| Hot Reload 注入 | DevTools → Service Protocol | 开发者保存文件 | reassemble() → rebuild |
| 字体变更 | Platform Thread → ReloadSystemFonts() |
系统字体更新 | 框架层自动处理 |
| Isolate Spawn | Dart Isolate.spawn() |
创建后台 isolate | 新 isolate 的 main() |
图片解码完成回调值得单独说明,因为它展示了典型的"三线程协作"模式:
[IO Thread] decodeImage(buffer) → 上传纹理到 GPU
│
│ PostTask(UITaskRunner, imageReady)
▼
[UI Thread] ImageStream.complete() → listener.onImage()
│ → 更新 Image widget 状态
│ → 如果需要,调用 RequestFrame()
▼
[UI Thread] build() → 插入新 Image 到 Widget 树
图片在 IO Thread 上解码并上传到 GPU,然后在 UI Thread 上触发回调刷新界面。解码耗时不会阻塞 UI Thread。
5.2 Raster Thread 的其他事件
Raster Thread 的核心职责是消费 Pipeline 中的 LayerTree 并执行光栅化,此外还处理:
| 事件 | 来源 | 说明 |
|---|---|---|
| Surface 创建/销毁/变更 | Platform Thread → NotifyCreated/Changed/Destroyed |
XComponent/SurfaceView 生命周期,需在 Raster Thread 上绑定上下文 |
| GPU 上下文切换 | GraphicsContext::MakeCurrent() / ResourceContextMakeCurrent() |
确保 Raster/IO 线程各自持有正确的 GPU 上下文 |
| 外部纹理帧通知 | 平台层 → MarkTextureFrameAvailable() |
视频播放器/相机产生新帧后通知 Engine 在下一帧合成该纹理 |
| GPU 资源缓存清理 | 低内存警告 → Rasterizer::ClearCache() |
释放 Skia/Impeller 持有的 GPU 缓存纹理 |
| 截图 | DevTools / SystemUI → Rasterizer::Screenshot() |
获取当前帧的像素图(用于性能 overlay、无障碍等) |
| DisplayList 缓存 | Engine 内部 | 无变化区域的 DisplayList 缓存命中时直接复用,跳过重新绘制 |
5.3 IO Thread 的其他事件
IO Thread 的工作模式是"队列消费"——UI Thread 或 Engine 初始化时向 IO Thread 投递任务,它逐个处理:
| 事件 | 来源 | 说明 |
|---|---|---|
| 图片解码 | Dart ImageProvider → IO TaskRunner |
解码 JPEG/PNG/WebP/GIF/HEIF,含解码器选择 |
| 纹理 GPU 上传 | IO Thread 内 | 解码后的像素数据通过 IO 线程的 GrDirectContext 上传到 GPU |
| Asset 资源加载 | Engine 启动 / rootBundle.load() |
从 flutter_assets/ 加载 Kernel 文件、ICU 数据、字体 |
| 字体文件读取 | SkFontMgr 初始化 |
扫描系统字体目录,创建 SkTypeface |
| Watchdog 监控 | OHOS 平台特有 | 周期性检查 UI Thread 是否卡死(超时 5s 则上报) |
| 资源缓存清理 | 低内存警告 | 释放 IO 线程持有的 GPU 资源上下文 |
关于 IO Thread 的一个常见误解:IO Thread 确实创建了 GPU 上下文(GrDirectContext),但它不参与帧渲染的主路径。它只负责"提前把纹理上传到 GPU 内存",让 Raster Thread 在渲染时直接使用已上传的纹理,无需等待。
5.4 Platform Thread 的其他事件
Platform Thread 是宿主线程,Flutter 在它上面注册的 TaskRunner 主要用于以下场景:
| 事件 | 方向 | 说明 |
|---|---|---|
| 平台 API 桥接回调 | 平台 → C++ | 平台原生方法调用与结果返回(JNI/NAPI/FFI 等) |
| Plugin 生命周期 | Dart → Platform | onAttach/onDetach 注册和释放平台插件 |
| Accessibility 服务 | 平台 → C++ → Dart | 无障碍焦点导航、语义节点查询 |
| 文本输入 | 平台 → Dart | 输入法连接、文本变更通知、光标位置 |
| 剪贴板 | Dart → Platform | 读取/写入系统剪贴板 |
| Haptic 反馈 | Dart → Platform | 调用系统震动 API |
| 返回键/Deep Link | 平台 → Dart | 系统返回键拦截、URL Scheme 分发 |
| 页面转场动画 | 平台 → Dart | iOS 侧滑返回手势协调 |
5.5 Worker Threads(Dart 并发池)
Dart VM 维护一组 Worker 线程(ConcurrentMessageLoop),用于执行:
| 事件 | 说明 |
|---|---|
Isolate.spawn() |
创建新 Dart Isolate 并执行 |
compute() / Isolate.run() |
后台执行 CPU 密集型计算 |
| Dart VM 内部 GC 并发标记 | CMS/CMC GC 的并发阶段 |
| Dart VM 内部 JIT 编译 | 热点代码编译为机器码 |
Worker 线程不是 Engine 的 ThreadHost 管理的,它们属于 Dart VM 内部。
5.6 按线程的全景分类
| 线程 | 事件数量 | 典型响应延迟要求 | 是否会阻塞 Dart |
|---|---|---|---|
| UI Thread | 密集(~10 种) | < 16ms (60Hz) | — |
| Raster Thread | 中等(~7 种) | < 16ms (60Hz) | 不直接阻塞 |
| IO Thread | 稀疏(~6 种) | 无硬性要求 | 不阻塞 |
| Platform Thread | 中等(~10 种) | < 100ms | 通过 PostTask 投递 |
| Worker Threads | 按需 | 无硬性要求 | 通过 Isolate 通信 |
UI Thread 是负载最重的线程——它必须处理来自所有其他线程的回调,同时执行 Dart 代码的帧渲染。这也解释了为什么 Flutter 花大量精力设计 TaskSourceGrade 优先级和帧期间暂停机制:避免非关键事件干扰帧渲染的原子性。
六、总结
回到四个问题:
1. VSync / UI 交互 / PlatformChannel 的异同?
三者都通过 PostTask(UIRunner, ...) 注入 Dart UI Thread,但 VSync 是唯一周期驱动且必然触发渲染的事件;UI 交互是事件驱动、选择性触发渲染;PlatformChannel 是双向通信、触发渲染与否取决于 Dart handler。
2. 三类事件在同一个队列吗?顺序如何?
是,都进入 UI TaskRunner 的同一个 TaskQueue。但带优先级:kUserInteraction(VSync、触摸) > kDartEventLoop(Timer/Future) > kUnspecified(普通任务)。帧期间 Dart EventLoop 任务还会被暂停,确保帧的原子性。
3. 宿主线程和 Dart UI Thread 如何互相触发?
单向的。宿主→Dart 通过 PostTask(UIRunner, ...) 投递事件;Dart→宿主必须经过 C++ 层的 PlatformMessageHandler,再由它 PostTask(PlatformRunner, ...) 投递到宿主线程。Dart 代码永远不能直接调用平台 API。
4. Flutter 线程如何适配平台?
Flutter 定义了三个适配接口:MessageLoopImpl(事件循环的创建/运行/终止)、Wakeable(线程唤醒)、VsyncWaiter(VSync 信号等待)。每个平台只需要实现这四个抽象方法(约 300 行代码),即可将 Flutter 的 4 线程模型映射到任意操作系统上。

浙公网安备 33010602011771号