首页
/ Flutter 自定义引擎 Embedder 接入 DDS 与 DevTools:`dart development-service` 与 `dart devtools` 实战指南

Flutter 自定义引擎 Embedder 接入 DDS 与 DevTools:`dart development-service` 与 `dart devtools` 实战指南

2026-09-06 15:55:55作者:齐添朝

当你绕过 flutter CLI、通过 C 层 Embedder API 自行托管 Flutter 引擎(例如 GLFW/自研窗口框架上的桌面宿主)时,Dart 调试链路会缺失关键一环:Dart Development Service(DDS)。本文基于 Flutter 仓库引擎文档 Using the Dart Development Service (DDS) and Flutter DevTools with a custom Flutter Engine Embedding-and-Flutter-DevTools-with-a-custom-Flutter-Engine-Embedding.md) 展开,说明该场景下会遇到的具体错误、DDS 在 VM Service 之上的定位,以及使用 Dart SDK 自带命令手动拉起 DDS 并接入 DevTools 的完整操作路径,帮助你为自研 Embedder 补齐日志历史、事件回放与 DevTools 调试能力。

问题场景:自定义 Embedder 启动应用后的报错

Flutter 引擎对窗口框架没有绑定,如果目标平台不是开箱支持的 iOS/Android,开发者需要基于 embedder API 自行构建宿主。该 API 以 GN target //shell/platform/embedder:flutter_engine 产出的动态库形式提供,整个 API 由单一 C 头文件 embedder.h 定义(见 BUILD.gn),仓库内提供了使用 GLFW 做窗口管理与渲染的示例实现 FlutterEmbedderGLFW.cc 可作参考。

当你用这类自定义 Embedder 启动 Flutter 应用后,如果直接去连接引擎打印出的 VM Service URI,通常会遇到如下错误:

This VM does not have a registered Dart Development Service (DDS) instance and is not currently serving Dart DevTools.

原因在于:这个流程绕过了 flutter CLI,而 flutter 工具在正常调试链路上正是负责启动 DDS 实例的一方。DDS 是 Dart VM Service 之上的一层中间件(middleware),提供日志历史、事件历史等额外能力,并且可以被配置为直接承载 DevTools 开发者工具集。自定义引擎 Embedding 的开发者有两种方式手动补上这一环:

  1. 使用 Dart SDK 自带的 dart development-service 命令(官方推荐);
  2. 使用 Dart SDK 自带的 dart devtools 启动一个 Flutter DevTools 实例,并把 VM Service URI 作为参数传入(例如 dart devtools http://localhost:8181)。

方式一(推荐):使用 dart development-service 启动 DDS

DDS 通过 dart development-service 命令启动。该命令的完整接口如下,可对服务的绑定地址、端口、鉴权与 DevTools 承载方式进行配置:

Start Dart's development service.

Usage: dart [vm-options] development-service [arguments]
-h, --help                                 Print this usage information.
    --vm-service-uri=<uri> (mandatory)     The VM service URI DDS will connect to.
    --bind-address=<address>               The address DDS should bind to.
                                           (defaults to "localhost")
    --bind-port=<port>                     The port DDS should be served on.
                                           (defaults to "0")
    --[no-]disable-service-auth-codes      Disables authentication codes.
    --[no-]serve-devtools                  If provided, DDS will serve DevTools. If not specified, "--devtools-server-address" is ignored.
    --devtools-server-address              Redirect to an existing DevTools server. Ignored if "--serve-devtools" is not specified.
    --[no-]enable-service-port-fallback    Bind to a random port if DDS fails to bind to the provided port.
    --cached-user-tags                     A set of UserTag names used to determine which CPU samples are cached by DDS.
    --google3-workspace-root               Sets the Google3 workspace root used for google3:// URI resolution.

Run "dart help" to see global options.

结合上述接口,各关键参数的取值语义如下:

参数 默认值 作用
--vm-service-uri (必填) DDS 要连接的 Dart VM Service URI,即 Flutter 引擎在自定义 Embedder 场景下输出的 VM Service 地址
--bind-address localhost DDS 对外提供服务的绑定地址
--bind-port 0(随机端口) DDS 的服务端口
--[no-]disable-service-auth-codes 鉴权码开启 关闭后可生成不带鉴权码的服务 URI
--[no-]serve-devtools 不承载 指定后由 DDS 的 HTTP 服务器直接承载 Dart SDK 自带的 DevTools;未指定时 --devtools-server-address 会被忽略
--devtools-server-address 重定向到一个已存在的 DevTools 服务器,仅当指定了 --serve-devtools 时生效
--[no-]enable-service-port-fallback 关闭 DDS 绑定指定端口失败时回退到随机端口
--cached-user-tags 指定一组 UserTag 名称,用于决定 DDS 缓存哪些 CPU 采样
--google3-workspace-root 设置 google3:// URI 解析所用的 Google3 工作区根目录

其中 --vm-service-uri 为必选项,指定由 Flutter 引擎提供的 Dart VM Service 的 URI;指定 --serve-devtools 后,Dart SDK 内置的 DevTools 实例将由 DDS 的 HTTP 服务器直接对外提供。

命令输出与 URI 切换规则

执行命令后,DDS 会将 JSON 编码的连接信息打印到 stdout

$ dart development-service --vm-service-uri=http://127.0.0.1:59113/BBPoXnZUWFU=/ --serve-devtools
{"state":"started","ddsUri":"http://127.0.0.1:59123/tbrR0DzW2j8=/","devToolsUri":"http://127.0.0.1:59123/tbrR0DzW2j8=/devtools?uri=ws://127.0.0.1:59123/tbrR0DzW2j8=/ws","dtd":{"uri":"ws://127.0.0.1:59122/R1LbdlhtkUygRWNA"}}

输出中各字段含义:

  • state:启动状态,started 表示 DDS 已就绪;
  • ddsUri:DDS 自身的 HTTP 服务 URI,后续所有 VM Service 请求都应通过该 URI 发起,而不是原始 VM Service URI;
  • devToolsUri:由 DDS 承载的 DevTools 页面入口,其中 ?uri= 查询参数携带了 DDS 的 WebSocket 端点;
  • dtd:Dart Tooling Daemon 的 WebSocket 地址。

这里有一条重要的连接纪律:DDS 启动后,所有 VM Service 请求都应经由 DDS 给出的 URI 进行,而不是原始的 VM Service URI。在独立 Dart VM(即 dart 命令)和 flutter 工具的场景中,原始 VM Service URI 会被隐藏,DDS URI 才是对外公布的 "Dart VM Service URI"——这样做的目的是降低开发者误连 VM Service 的概率。需要注意:对已经挂载了活跃 DDS 实例的 VM Service 发起直连请求会被拒绝。对自定义 Embedder 而言,这意味着你在宿主程序(如基于 GLFW 的示例 Embedder)里拿到引擎输出的原始 URI 后,应当把调试工具指向 DDS 的 ddsUri,而不是直接用原始 URI。

生命周期方面,DDS 实例会在目标应用关闭时自动退出,无需手动做生命周期管理——这对以宿主进程形式长时间运行的自定义 Embedder 场景尤为重要:调试会话结束时 DDS 会随应用一起回收,不会残留孤儿进程。

方式二:使用 dart devtools 直接拉起 DevTools

另一条路径是用 dart devtools 命令启动 DevTools,并把 VM Service URI 作为位置参数传入,这样 DevTools 实例会自动连接到指定的目标应用。但该命令在直连 VM Service 之前有一个前置检查:先判断给定的 VM Service URI 是否指向一个 DDS 实例。如果不是,命令会自行启动 DDS、把 DDS URI 打印到控制台,然后启动一个直连 DDS 实例(而非传入的 VM Service URI)的 DevTools:

$ dart devtools http://127.0.0.1:59251/2LS6f3Kb2JI=/
Started the Dart Development Service (DDS) at http://127.0.0.1:59260/38XeuQpIHRE=/
Serving DevTools at http://127.0.0.1:9101.

          Hit ctrl-c to terminate the server.

与方式一的关键差异:DevTools 的存活边界

两种方式下,DDS 的生命周期都直接绑定在其所连接的应用生命周期上。但存在一个关键区别:通过 dart devtools 启动时,DevTools 并不是由 DDS 承载的——也就是说,即使 DDS 依然附着在目标应用上,一旦 dart devtools 进程被杀死,DevTools 也随之不可用。而使用 dart development-service --serve-devtools 时,DevTools 由 DDS 的 HTTP 服务器提供,只要 DDS 存活(即目标应用存活),DevTools 就一直可访问。

综合来看:如果你需要在自研宿主中给多路调试工具(IDE、性能分析脚本等)提供稳定的 VM Service 入口,方式一是更稳妥的选择;如果你只是临时想打开一个 DevTools 页面做快速检查,方式二更为直接,但要知道它的 DevTools 服务随该命令进程终止而消失。

在自定义 Embedder 中落地的要点

结合上文,把这套流程落到一个自定义 Embedder 工程中的要点如下:

  1. 先拿到 VM Service URI。自定义 Embedder 在调试模式启动引擎时,Dart VM Service 会随引擎启动,引擎会输出其连接 URI(形如 http://127.0.0.1:<port>/<authcode>=/)。在 embedder.h 定义的 Embedder API 中,引擎侧的消息可通过 FlutterProjectArgs 中的日志回调(如 log_message_callback)等通道暴露给宿主,开发者可以从引擎的日志输出中捕获该 URI;GLFW 示例展示了完整的最小 Embedder 结构,可作为接线参考。
  2. 选择接入命令。日常调试建议使用 dart development-service --vm-service-uri=<引擎输出的URI> --serve-devtools,解析 stdout 中的 JSON 拿到 ddsUridevToolsUri;临时排查可用 dart devtools <引擎输出的URI> 一步到位。
  3. 始终经由 DDS 发起 VM Service 请求,避免直连被拒绝;利用 DDS 自动随应用退出的特性,无需在宿主中编写清理逻辑。
  4. 若你的 Embedder 涉及更底层的调试链路(本地引擎构建与 --local-engine 运行方式),可参考 调试引擎指南;若关心 VM Service 上 Flutter 引擎特有的协议扩展,可参考 Engine-specific Service Protocol extensions

小结

在 Flutter 自定义引擎 Embedding 场景中,缺失 DDS 会直接导致 "is not currently serving Dart DevTools" 类错误。仓库文档给出的两条修复路径——dart development-service(推荐,DDS 承载 DevTools、生命周期自管理)与 dart devtools(便捷但 DevTools 随进程消亡)——均以 Dart SDK 自带命令实现,无需在宿主侧编写额外服务代码。掌握 --vm-service-uri--serve-devtools 等参数语义与 ddsUri 的连接纪律,即可让自研宿主的 Flutter 应用具备与 flutter run 对等的调试体验。

登录后查看全文
热门项目推荐
相关项目推荐