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_timeframe_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 QueueEvent 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 阻塞等待 ← 风险点

这种同步等待是死锁高风险区域,必须满足两个条件才能安全使用:

  1. 目标线程不是当前线程(否则死锁)
  2. 目标线程当前的执行流不会等待 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 线程模型映射到任意操作系统上。

posted @ 2026-06-12 18:01  getmoon  阅读(17)  评论(0)    收藏  举报