首页
/ Flutter 引擎嵌入器(Embedder API):为 Flutter 未开箱支持的平台编写自定义宿主

Flutter 引擎嵌入器(Embedder API):为 Flutter 未开箱支持的平台编写自定义宿主

2026-09-06 15:09:56作者:彭桢灵Jeremy

本文基于仓库文档 Custom Flutter Engine Embedders,讲解如何借助 Flutter Engine 的嵌入器 C API(flutter_engine 动态库 + 单一 embedder.h 头文件)为 iOS/Android 之外的平台构建自己的 Flutter 宿主环境:从获取或构建引擎产物,到走通仓库自带的 GLFW 示例工程,再到理解 FlutterEngineRun 等核心 API 的签名与参数约定。读完后你将具备在不被官方运行时直接支持的硬件(如嵌入式设备)上嵌入并运行 Flutter 应用的完整方案。需要预先说明:这是一套非常底层的 API,不适合初学者,且自定义引擎构建不被官方支持,应只作为短期方案(详见文末支持策略)。

为什么需要自定义 Embedder

Flutter Engine 本身是窗口工具包无关(window toolkit agnostic)的。iOS 与 Android 平台之所以"开箱即用",是因为 Flutter 为它们发布了完整运行时;如果你要在其他平台(例如嵌入式硬件、自定义桌面窗口管理器)运行 Flutter 应用,就必须自己实现一个嵌入器(embedder):负责创建窗口/渲染目标、把输入事件喂给引擎、把引擎绘制的帧提交到屏幕。

官方文档明确给出的定位是:

  • 这套 API 非常底层(very low level),不适合初学者;
  • 引擎中窗口工具包无关的部分以动态库形式提供,位于 GN 目标 //shell/platform/embedder:flutter_engine
  • 引擎 API 没有任何平台特定依赖,拥有稳定 ABI(stable ABI),全部接口都集中在一个 C 头文件中。

在当前仓库中,这些组件的实际位置如下(monorepo 后位于 engine/src/flutter 之下):

组件 仓库路径
嵌入器 C API 头文件(全部 API 声明) embedder.h
嵌入器 API 实现 embedder.cc
flutter_engine GN 目标定义 BUILD.gn
Linux 动态库符号导出白名单 embedder_exports.lst
官方参考示例(GLFW 窗口) FlutterEmbedderGLFW.ccrun.sh

获取 flutter_engine 动态库:构建或下载

GN 目标 flutter_engine 的产物构成

文档指出,动态库位于 //shell/platform/embedder:flutter_engine 这个 GN 目标中,并且"必须作为宿主(host)GN 构建的一部分来构建"。桌面 Linux 与 Mac 的宿主构建已经在构建机器人上常态化进行,如果要面向另一个平台,你需要为其配置一套 GN 工具链。

从源码结构看,BUILD.gngroup("flutter_engine")(约 L567-L585)聚合了所有平台都需要的两个依赖:

  • :copy_headers:把 embedder.h 复制为产物目录下的 flutter_embedder.h(见 BUILD.gn#L472-L476),这样嵌入器只需要包含一个头文件;
  • :flutter_engine_libraryshared_library 目标,output_name = "flutter_engine",即 Linux 下的 libflutter_engine.so、Windows 下的 flutter_engine.dll、macOS 下的 libflutter_engine.dylib

各平台的打包形态可以进一步在 BUILD.gn 中确认:

  • macOS:dylib 被打包进 FlutterEmbedder.framework(含头文件、modulemap、Info.plist 与 ICU 数据),并由 zip_bundle("flutter_embedder_framework_archive")(约 L587-L610)产出 FlutterEmbedder.framework.zip
  • Linux / Windows / Androidzip_bundle("embedder-archive")(约 L612-L647)产出 <平台名>-embedder.zip,内容固定为 flutter_embedder.h + 对应平台的 libflutter_engine.so,或 flutter_engine.dll + flutter_engine.dll.lib

值得注意的工程细节:Linux 构建通过 --version-script 链接 embedder_exports.lst(见 BUILD.gn#L462-L465)。这个白名单文件只导出 Flutter*__Flutter*kFlutter*kDartSnapshotDatakDartSnapshotTextInternalFlutterGpu* 等符号,其余全部 local —— 这正是"稳定 ABI"在链接层面的体现:嵌入器只能依赖公开 API,无法意外绑到内部符号。

使用构建机器人上传的预编译产物

文档同时说明,你可以自己构建,也可以直接下载构建机器人为每个 commit 上传的产物。官方给出的地址模板如下(将 FLUTTER_HASH 替换为你要使用的 Flutter commit SHA):

# macOS(x64)
https://storage.googleapis.com/flutter_infra_release/flutter/FLUTTER_HASH/darwin-x64/FlutterEmbedder.framework.zip

# Linux(x64)
https://storage.googleapis.com/flutter_infra_release/flutter/FLUTTER_HASH/linux-x64/linux-x64-embedder.zip

# Windows(x64)
https://storage.googleapis.com/flutter_infra_release/flutter/FLUTTER_HASH/windows-x64/windows-x64-embedder.zip

文档对 Linux 产物有一个明确提醒:该二进制没有 strip,包含调试信息;嵌入器应在部署前自行 strip。这也解释了为什么预编译的 libflutter_engine.so 体积偏大。

参考实现:GLFW 示例工程

官方文档建议以 examples/glfw 中的 GLFW 示例作为编写嵌入器的指南。该示例在仓库内自带完整 README,其运行方式对理解"一个最小可用嵌入器需要做什么"非常有价值。

依赖与运行步骤

examples/glfw/README.md 列出四个依赖:GLFW(brew install glfw)、CMake(brew install cmake)、Flutter SDK、Flutter Engine(自行构建或按上一节下载)。在示例目录下执行 ./run.sh 即可完成构建与运行。run.sh 的完整流程分三步:

  1. 构建宿主 C++ 工程cmake -DCMAKE_BUILD_TYPE=Debug -DFLUTTER_ENGINE_VARIANT=<variant> .. + make,其中 variant 按 CPU 架构自动选择 host_debug_unopt_arm64host_debug_unopt
  2. 构建 Flutter 客户端工程flutter create myapp,把示例的 main.dart 拷入后执行 flutter build bundle --local-engine-src-path ../../../../../ --local-engine=$variant --local-engine-host=$variant, 生成 build/flutter_assets 目录(含 kernel_blob.bin);
  3. 运行嵌入器./flutter_glfw ./myapp <path-to-icudtl.dat>,第一个参数是 Flutter 工程路径,第二个是 icudtl.dat(可从 Flutter 缓存或引擎产物中获取)。

CMakeLists.txt 展示了引擎库的接入方式:头文件直接包含 shell/platform/embedder 目录(即 embedder.h),动态库通过 find_library(FLUTTER_LIB flutter_engine PATHS .../out/${FLUTTER_ENGINE_VARIANT}) 定位本地构建输出,并在 POST_BUILD 阶段把 dylib 拷贝到构建目录以便运行时加载。

最小嵌入器代码解析

FlutterEmbedderGLFW.cc 仅约 200 行,覆盖了一个嵌入器的全部关键职责,逐段对照如下:

(1)版本断言。文件开头对 API 版本做了硬断言(FlutterEmbedderGLFW.cc#L18-L22):

static_assert(FLUTTER_ENGINE_VERSION == 1,
              "This Flutter Embedder was authored against the stable Flutter "
              "API at version 1. There has been a serious breakage in the "
              "API. Please read the ChangeLog and take appropriate action "
              "before updating this assertion");

FLUTTER_ENGINE_VERSION 定义于 embedder.h#L72,当前值为 1。这是稳定 ABI 的"握手"机制:升级引擎头文件时若大版本不匹配,编译期就会强制你阅读变更说明。

(2)创建渲染器配置并启动引擎RunFlutterL92-L138)构造 FlutterRendererConfigFlutterProjectArgs 后调用 FlutterEngineRun

FlutterRendererConfig config = {};
config.type = kOpenGL;
config.open_gl.struct_size = sizeof(config.open_gl);
config.open_gl.make_current = [](void* userdata) -> bool {
  glfwMakeContextCurrent(static_cast<GLFWwindow*>(userdata));
  return true;
};
config.open_gl.clear_current = [](void*) -> bool {
  glfwMakeContextCurrent(nullptr);
  return true;
};
config.open_gl.present = [](void* userdata) -> bool {
  glfwSwapBuffers(static_cast<GLFWwindow*>(userdata));
  return true;
};
config.open_gl.fbo_callback = [](void*) -> uint32_t {
  return 0;  // FBO0
};
config.open_gl.gl_proc_resolver = [](void*, const char* name) -> void* {
  return reinterpret_cast<void*>(glfwGetProcAddress(name));
};

std::string assets_path = project_path + "/build/flutter_assets";
FlutterProjectArgs args = {
    .struct_size = sizeof(FlutterProjectArgs),
    .assets_path = assets_path.c_str(),
    .icu_data_path = icudtl_path.c_str(),
};
FlutterEngine engine = nullptr;
FlutterEngineResult result =
    FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, window, &engine);

这里可以看到嵌入器与引擎的契约全貌:

  • 渲染器配置是回调式注入——引擎不关心窗口怎么创建、帧怎么呈现,make_current / present / fbo_callback / gl_proc_resolver 由宿主提供。FlutterRendererConfig 是一个带 type 字段与匿名 union 的联合体(embedder.h#L1047-L1055),可选 kOpenGLkSoftwarekMetal(仅 Darwin)、kVulkanembedder.h#L81-L89)。软件渲染(kSoftware)只需提供一个 32 位 RGBA 缓冲的 surface_present_callback,对没有 GPU 的嵌入式硬件尤其重要;
  • FlutterProjectArgs.assets_path 指向 flutter build bundle 生成的 build/flutter_assets(内含 Dart kernel 与字体等资源),icu_data_path 指向 ICU 数据文件。FlutterProjectArgs 的完整字段(命令行参数、平台消息回调、AOT 快照缓冲区、语义回调等)定义于 embedder.h#L2527 起
  • 启动成功后把 engine 句柄存入 GLFW window 的 user pointer,供后续事件回调使用。

(3)把窗口事件翻译为引擎事件。窗口尺寸变化时构造 FlutterWindowMetricsEvent 并调用 FlutterEngineSendWindowMetricsEventL78-L90);鼠标按下/移动/释放时构造 FlutterPointerEvent(带 kDown/kMove/kUp 相位、微秒级时间戳、像素坐标按 g_pixelRatio 缩放)并调用 FlutterEngineSendPointerEventL24-L43)。所有事件都带 view_id——示例只使用隐式视图(id 0),而多窗口嵌入器需要通过 FlutterEngineAddView 等 API 管理多个视图。

(4)像素比修正。示例用 glfwGetFramebufferSize 与初始窗口宽度相除得到 g_pixelRatioL177-L179)。README 的 Troubleshooting 一节提醒:如果渲染缩放不对,需要调整该值;找不到 GLFW 或引擎库时则修改 CMakeLists.txt 中的搜索路径。

核心 API 速览与 ABI 约定

embedder.h(约 3800 行)声明了完整的 C API。对嵌入器开发者最关键的几组接口(声明见 embedder.h#L2885-L2975):

// 版本:必须传 FLUTTER_ENGINE_VERSION
typedef enum {
  kSuccess = 0,
  kInvalidLibraryVersion,
  kInvalidArguments,
  kInternalInconsistency,
} FlutterEngineResult;

// 一步式:初始化并运行(等价于 Initialize + RunInitialized)
FLUTTER_EXPORT
FlutterEngineResult FlutterEngineRun(size_t version,
                                     const FlutterRendererConfig* config,
                                     const FlutterProjectArgs* args,
                                     void* user_data,
                                     FLUTTER_API_SYMBOL(FlutterEngine)* engine_out);

// 分步式:先拿句柄再运行
FLUTTER_EXPORT
FlutterEngineResult FlutterEngineInitialize(size_t version,
                                            const FlutterRendererConfig* config,
                                            const FlutterProjectArgs* args,
                                            void* user_data,
                                            FLUTTER_API_SYMBOL(FlutterEngine)* engine_out);

// 关闭实例;此后句柄不可再用于任何 API 调用
FLUTTER_EXPORT
FlutterEngineResult FlutterEngineShutdown(FLUTTER_API_SYMBOL(FlutterEngine) engine);

头文件注释明确解释了 Initialize/RunInitialized 分步流程存在的意义:当嵌入器通过 FlutterProjectArgs::custom_task_runners 提供自定义任务运行器时,引擎可能在 FlutterEngineRun 返回之前就需要向嵌入器投递任务,而嵌入器只有拿到句柄后才能完成任务投递——这种场景下应先 FlutterEngineInitialize 拿到句柄,再调用 FlutterEngineRunInitialized

其他值得注意的 ABI 约定:

  • 结构体自描述尺寸:所有传入引擎的结构体(FlutterProjectArgs、各 Flutter*RendererConfig、事件结构体)第一个字段都是 struct_size,必须填 sizeof(对应结构体)。这让引擎在未来给结构体追加字段时仍能与旧嵌入器二进制兼容;
  • 符号前缀机制embedder.h 支持 FLUTTER_API_SYMBOL_PREFIXembedder.h#L62-L70),允许同一进程内嵌入多个带不同前缀的引擎副本;
  • 导出面最小化:如前所述,embedder_exports.lst 只导出 Flutter* 等公开符号族,local: * 兜底隐藏一切内部符号;
  • 事件入口 API:窗口指标(FlutterEngineSendWindowMetricsEvent)、指针(FlutterEngineSendPointerEvent)、按键(FlutterEngineSendKeyEvent)、平台消息(FlutterEngineSendPlatformMessage)、外部纹理注册(FlutterEngineRegisterExternalTexture)、语义(FlutterEngineUpdateSemanticsEnabled 等)等均以函数指针 typedef 汇总声明于 embedder.h#L3646-L3770 附近,嵌入器可据此按需接入输入、互操作与无障碍能力。

嵌入器侧的实现主体在 embedder.cc(约 3800 行)与 embedder_engine.cc,按渲染目标拆分出 Skia/Impeller、GL/Metal/Vulkan/Software 等多种 surface 实现(如 embedder_surface_vulkan.ccembedder_surface_software.cc),并有配套单元测试(platform_view_embedder_unittests.cctests/ 目录)验证这些接口行为。

支持策略:把自定义引擎构建当作短期方案

文档最后两段是重要且容易被忽略的约定,直接决定了团队采用这套 API 时的预期管理:

  • Flutter 团队不反对为特定目的做自定义引擎构建,但不承诺任何修复时间线——即使是对通常愿意提供承诺的客户也是如此(参考 Issue Hygiene 说明);
  • 官方不预期自定义引擎构建是长期可持续的:在 Flutter 计划自行发布独立运行时(如 Web 与桌面)的平台上,这类构建注定无法持续;每次 Flutter 新增特性,自定义构建都需要跟随更新,维护成本高昂;
  • 官方一般建议只在把 Flutter 移植到未开箱支持的平台时(例如嵌入式硬件)使用自定义引擎构建,并尽快过渡离开这类配置。

因此,实践上的合理姿势是:用 embedder.h + flutter_engine 动态库打通目标硬件上的最小链路(GLFW 示例即起点),把平台窗口/输入/渲染胶水代码集中封装,并持续跟随引擎更新,而不是把它当作一个可以长期冻结的私有依赖。

小结

  • Flutter Engine 的嵌入器 API 以单一 C 头文件 embedder.h + flutter_engine 动态库的形式提供,稳定 ABI、版本宏 FLUTTER_ENGINE_VERSION(当前为 1)、struct_size 自描述结构体与最小符号导出共同保证了跨版本兼容性;
  • 获取引擎的方式有二:在 host GN 构建中构建 //shell/platform/embedder:flutter_engineBUILD.gn),或按 commit SHA 下载构建机器人上传的 platform-embedder 压缩包(Linux 产物未 strip,部署前需自行 strip);
  • examples/glfw 给出了最小可运行的完整嵌入器:渲染器回调注入 → FlutterEngineRun 启动 → 窗口/指针事件回调转发,配合 run.sh 的三步脚本即可在本地跑通;
  • 该配置不受官方支持、无修复时限承诺,官方建议仅将其用于嵌入式等未开箱平台,并视为短期方案。
登录后查看全文
热门项目推荐
相关项目推荐