- 跨平台
- 图形学
- 前端
【免费下载链接】engine
The Flutter engine
导读
本文以 Flutter Engine 仓库中的 GLFW 嵌入示例(examples/glfw/README.md)为主线,讲解如何通过 C APIFlutterEngineRun把 Flutter 应用渲染到一个全新的宿主环境——GLFW 窗口,并配套深度解析构建脚本、CMake 配置、事件转发与 ABI 约束。读完本文,你将掌握 Flutter Embedder 的完整接入链路:从 CMake 链接引擎动态库、flutter build bundle产出资产,到用 OpenGL 回调接管渲染、把鼠标/窗口事件转发给引擎,以及排查像素比与库路径等经典问题。
一、背景:什么是 Flutter Engine Embedder
Flutter Engine 本身是"窗口工具包无关"(window toolkit agnostic)的:它不关心你的应用跑在哪个窗口系统里,只要宿主环境(embedder)提供渲染回调与事件通道,它就能完成布局、绘制与 Dart 代码执行。官方文档 Custom-Flutter-Engine-Embedders 明确指出,这套 API 面向的是"需要把 Flutter 移植到 iOS / Android 之外平台"的开发者,属于底层接口,不适合初学者。
仓库中的 examples/glfw 就是官方给出的最小参考实现:它把 Flutter 应用渲染进 GLFW 创建的窗口,覆盖了嵌入一个引擎所需的全部核心步骤:
- 用 CMake 把宿主 C++ 程序与 Flutter 引擎动态库链接起来;
- 用
flutter build bundle生成 Flutter 项目的资产目录(flutter_assets); - 通过
FlutterEngineRun启动引擎,并注册 OpenGL 渲染回调; - 把 GLFW 的鼠标、窗口尺寸事件转发给引擎。
示例只支持单窗口,因此全部事件都固定发往唯一的"隐式视图"(implicit view,view_id = 0)。
官方同时提醒:自定义引擎构建不受官方支持,官方不承诺修复此类配置下的 bug,也不建议作为长期方案;这类示例主要用于把 Flutter 移植到尚未被官方覆盖的平台(如嵌入式硬件)。请把它当作学习 Embedder API 的教科书,而非生产级嵌入方案。
二、目录结构与文件职责
| 文件 | 职责 |
|---|---|
| examples/glfw/README.md | 示例说明、依赖、运行与排障指南 |
| examples/glfw/FlutterEmbedderGLFW.cc | 宿主 C++ 程序:GLFW 窗口 + Embedder 接入核心 |
| examples/glfw/CMakeLists.txt | 宿主程序的构建配置,负责链接 GLFW 与 Flutter 引擎库 |
| examples/glfw/run.sh | 一键构建 + 构建 Dart 工程 + 运行示例 |
| examples/glfw/main.dart | 被嵌入的 Flutter 应用(计数器 Demo) |
| examples/glfw/BUILD.gn | GN 构建入口,由build_embedder_examples开关控制 |
其中 BUILD.gn 表明,该示例也能作为引擎 GN 构建的一部分产出(输出名embedder_example),它依赖//flutter/shell/platform/embedder:embedder和//flutter/third_party/glfw两个目标;不过日常体验流程走的是 CMake +run.sh。
三、依赖与运行(原文档继承)
原 README 给出四条依赖,按其说明补齐即可:
- GLFW(窗口与 OpenGL 上下文):macOS 上可用 Homebrew 安装
brew install glfw;其他 *nix 平台请用对应包管理器; - CMake(≥ 3.15,见 CMakeLists.txt):
brew install cmake; - Flutter SDK:从 Flutter 官网安装,用于执行
flutter create与flutter build bundle; - Flutter Engine:可自行编译,也可直接下载 CI 产出的
flutter_engine动态库(详见 Custom-Flutter-Engine-Embedders 中的下载说明;注意区分 macOS 的FlutterEmbedder.framework与 Linux 的linux-x64-embedder)。
原文档声明该示例在 macOS 上测试通过,稍作调整即可运行于其他 *nix 平台与 Windows。构建并运行只需在示例目录执行:
./run.shrun.sh默认假设你已经在examples/glfw的上一级(即仓库根目录)构建过对应变体的 Flutter 引擎,因为 CMake 会去../../../out/<variant>找引擎库,详见下文。
四、run.sh逐步拆解:一次构建背后的完整链路
run.sh 以set -e开头(任一命令失败即退出),整体分三个阶段:
4.1 选择引擎变体
if uname -m | grep "arm64"; then variant="host_debug_unopt_arm64" else variant="host_debug_unopt" fi根据 CPU 架构选择host_debug_unopt(x86_64)或host_debug_unopt_arm64(Apple Silicon)。这是引擎 GN 构建的 host 调试未优化变体,与下文 CMake 的FLUTTER_ENGINE_VARIANT及flutter build bundle的--local-engine三处保持一致。
4.2 构建宿主 C++ 程序
mkdir -p debug && cd debug cmake -DCMAKE_BUILD_TYPE=Debug -DFLUTTER_ENGINE_VARIANT=$variant .. make生成debug/目录并用 CMake 配置、编译出可执行文件flutter_glfw。CMake 阶段的具体行为见 CMakeLists.txt:
set(FLUTTER_ENGINE_VARIANT "host_debug_unopt" CACHE STRING "")(L4):通过命令行-D覆盖的缓存变量;- GLFW 部分(L11-L17):关闭 GLFW 自身的示例/测试/文档构建后,以
add_subdirectory直接引入仓库内的third_party/glfw源码参与编译(add_subdirectory(${CMAKE_SOURCE_DIR}/../../third_party/glfw glfw)),并将third_party/glfw/include加入头文件搜索路径; - Flutter Engine 部分(L24-L26):
include_directories(${CMAKE_SOURCE_DIR}/../../shell/platform/embedder)指向embedder.h 头文件所在目录(即 shell/platform/embedder/embedder.h);随后find_library(FLUTTER_LIB flutter_engine PATHS ${CMAKE_SOURCE_DIR}/../../../out/${FLUTTER_ENGINE_VARIANT})在仓库根目录的out/<variant>下寻找flutter_engine动态库; - POST_BUILD 拷贝(L30-L34):构建完成后把引擎动态库复制到当前构建目录,因为最终运行时需要在可执行文件旁找到形如
libflutter_engine.dylib的共享库。
关键结论:
FLUTTER_ENGINE_VARIANT的值必须与out/目录下真实存在的引擎产物变体名一致,否则find_library失败。
4.3 构建被嵌入的 Flutter 工程
flutter create myapp cd myapp flutter pub add flutter_gpu --sdk=flutter cp ../../main.dart lib/main.dart flutter build bundle \ --local-engine-src-path ../../../../../ \ --local-engine=$variant \ --local-engine-host=$variant cd -flutter create myapp生成标准 Flutter 工程骨架;flutter pub add flutter_gpu --sdk=flutter引入 Flutter GPU 包(对应 main.dart 的import 'package:flutter_gpu/gpu.dart');cp ../../main.dart lib/main.dart用仓库自带的 main.dart 覆盖默认入口;flutter build bundle产出myapp/build/flutter_assets目录——这正是 FlutterEmbedderGLFW.cc 中注释所指向的资产目录;--local-engine-src-path ../../../../../(即仓库根目录)与--local-engine让 Flutter 工具链复用本地引擎产物,而不是下载线上引擎。
4.4 运行
./flutter_glfw ./myapp ../../../third_party/icu/common/icudtl.dat程序接收两个参数:Flutter 工程路径与icudtl.dat 路径。icudtl.dat是 ICU 国际化数据文件,这里直接使用引擎仓库third_party/icu/common/下的副本;README 提示,若用 Flutter SDK 内置缓存,则文件位于 Flutter SDK 的bin/cache目录。
五、核心代码剖析:FlutterEmbedderGLFW.cc
FlutterEmbedderGLFW.cc 是示例的"心脏",全部关键决策都集中在此。文件开头(L12-L16)定义了几个全局常量:
static double g_pixelRatio = 1.0; // 窗口创建后计算 static const size_t kInitialWindowWidth = 800; // 初始窗口宽 static const size_t kInitialWindowHeight = 600; // 初始窗口高 static constexpr FlutterViewId kImplicitViewId = 0; // 隐式视图 ID紧接着(L18-L22)是一个非常重要的 ABI 版本守卫:
static_assert(FLUTTER_ENGINE_VERSION == 1, "This Flutter Embedder was authored against the stable Flutter " "API at version 1. ...");5.1 main() 流程
main 函数 按以下顺序执行:
- 校验参数:
argc != 3时打印用法embedder_example <path to project> <path to icudtl.dat>并退出(L150-L153); - 初始化 GLFW:注册错误回调
GLFW_ErrorCallback后glfwInit()(L158-L164); - Linux 特殊处理:
#if defined(__linux__)下设置glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API)(L166-L168),强制 GLFW 用 EGL 创建 OpenGL 上下文,这是嵌入式环境常见的兼容性要求; - 创建窗口:
glfwCreateWindow(kInitialWindowWidth, kInitialWindowHeight, "Flutter", NULL, NULL)(L170-L171); - 计算像素比:通过
glfwGetFramebufferSize取得真实帧缓冲尺寸,g_pixelRatio = framebuffer_width / kInitialWindowWidth(L177-L179)——窗口尺寸与帧缓冲尺寸在 HiDPI 屏上可能不一致,像素比就是把逻辑坐标换算成物理像素的关键; - 启动引擎:
RunFlutter(window, project_path, icudtl_path)(L181); - 注册 GLFW 回调:键盘(Esc 关闭窗口)、窗口尺寸变化、鼠标按键(L187-L189);
- 事件循环:
while (!glfwWindowShouldClose(window)) glfwWaitEvents();(L191-L193),由 GLFW 驱动事件分发; - 清理:
glfwDestroyWindow与glfwTerminate(L195-L196)。
5.2 RunFlutter:渲染器配置与引擎启动
RunFlutter 是 Embedder 接入的精髓,分为两部分。
第一部分:填充FlutterRendererConfig(OpenGL 渲染器)
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); // is this even a thing? 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)); };对照 embedder.h 中的定义:
- FlutterRendererConfig 是一个
type + union结构,type取自 FlutterRendererType 枚举:kOpenGL、kSoftware、kMetal(仅 Darwin 平台)、kVulkan; - OpenGL 渲染器必须提供的关键回调(见 FlutterOpenGLRendererConfig 定义,示例使用的字段在 FlutterEmbedderGLFW.cc):
make_current:把某线程的 GL 上下文切换为当前(示例直接映射到glfwMakeContextCurrent);clear_current:清除当前上下文;present:一帧渲染完成后提交到屏幕(示例映射到glfwSwapBuffers);fbo_callback:返回引擎要渲染到的帧缓冲对象 ID,示例固定返回 0(默认 FBO);gl_proc_resolver:解析 GL 函数指针,示例映射到glfwGetProcAddress;make_resource_current:可选回调,用于后台线程异步纹理上传的共享上下文,对纹理性能有提升,示例未实现。
第二部分:填充FlutterProjectArgs并启动引擎
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); if (result != kSuccess || engine == nullptr) { std::cout << "Could not run the Flutter Engine." << std::endl; return false; } glfwSetWindowUserPointer(window, engine); GLFWwindowSizeCallback(window, kInitialWindowWidth, kInitialWindowHeight);对照 FlutterProjectArgs:
struct_size:ABI 版本协商的关键——所有嵌入器结构体都必须以sizeof(Type)初始化首成员(详见 embedder.h 头部的 ABI 规则注释 L12-L50);assets_path:指向flutter build bundle产出的flutter_assets目录(运行时 Dart 代码kernel_blob.bin及资源都从该目录加载,main_path__unused__、packages_path__unused__在 Dart 2 之后已废弃,仅为 ABI 稳定性保留);icu_data_path:icudtl.dat的路径;- 其余可选字段还包括
command_line_argc/argv(引擎 flag,注意第一个元素被视为可执行名)、platform_message_callback、AOT 模式下的 VM/isolate snapshot 缓冲区指针等。
启动函数 FlutterEngineRun 的签名是:
FlutterEngineResult FlutterEngineRun(size_t version, const FlutterRendererConfig* config, const FlutterProjectArgs* args, void* platform_data, // 透传给各回调的 userdata FlutterEngine* engine_out);platform_data参数把 GLFW 窗口指针透传给所有回调的userdata,这就是回调里static_cast<GLFWwindow*>(userdata)的由来。返回的 FlutterEngineResult 枚举包含kSuccess、kInvalidLibraryVersion、kInvalidArguments、kInternalInconsistency。启动成功后,把引擎句柄存入窗口用户指针(glfwSetWindowUserPointer),并主动补发一次窗口尺寸事件,让引擎在首帧前就知道画布大小。
5.3 事件转发:让 GLFW 事件流进 Flutter
指针(鼠标)事件:GLFWcursorPositionCallbackAtPhase 构造FlutterPointerEvent,关键字段包括:
struct_size = sizeof(event)(ABI 要求);phase:kMove/kDown/kUp等指针相位;x、y:乘以g_pixelRatio换算成物理像素后上报;timestamp:以微秒为单位的单调时钟;view_id = kImplicitViewId(单窗口固定 0)。
随后调用FlutterEngineSendPointerEvent(engine, &event, 1)把事件交给引擎。鼠标按下/抬起逻辑在 GLFWmouseButtonCallback 中:左键按下时上报kDown并开始跟踪光标位置(移动即上报kMove),释放时上报kUp并停止跟踪。
窗口尺寸事件:GLFWwindowSizeCallback 构造FlutterWindowMetricsEvent,上报width、height(均乘像素比)与pixel_ratio,再调用FlutterEngineSendWindowMetricsEvent。
这两个 API 的声明可在 embedder.h 中查到:FlutterEngineSendWindowMetricsEvent(engine, event)与FlutterEngineSendPointerEvent(engine, events, events_count)(后者支持一次批量发送多个指针事件)。
键盘事件:示例仅用GLFWKeyCallback处理 Esc 关闭窗口(L68-L76),并没有把键盘输入转发给引擎——这是该示例刻意保持精简的结果;生产级嵌入器应通过FlutterEngineSendKeyEvent(见 embedder.h 中 SendKeyEvent 相关声明)实现完整键盘链路。
六、Dart 端注意事项
被嵌入的 Flutter 应用 main.dart 有两个值得注意的点:
- 平台伪装(L21-L23):
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;——由于 GLFW 宿主不在 Flutter 官方支持的平台列表内,直接运行会触发 "unsupported platform" 报错,示例用这个 hack 让框架认为自己在 Fuchsia 上运行; - 强制实例化 Flutter GPU 上下文(L10-L19):
gpu.gpuContext的访问会强制 GPU 上下文实例化,从而确保 Flutter GPU 符号可用;若 Impeller 后端未启用,则断言其异常信息包含 "Flutter GPU requires the Impeller rendering backend to be enabled."。
应用本身是标准 Material 计数器 Demo(MyApp/MyHomePage/_incrementCounter),用于直观验证渲染与事件链路是否打通——按钮点击、计数刷新都依赖指针事件正确送达引擎。
七、Troubleshooting:常见问题与调优(原文档继承 + 扩展)
原 README 归纳了三类高频问题,结合源码给出具体处置方式:
7.1 Flutter Engine 位置不对
CMakeLists.txt 默认假设你已在仓库根目录out/host_debug_unopt[_arm64]构建了引擎(find_library ... PATHS ${CMAKE_SOURCE_DIR}/../../../out/${FLUTTER_ENGINE_VARIANT})。如果你的引擎在别处:
- 修改
find_library的PATHS,或新增-DFLUTTER_ENGINE_VARIANT=<你的变体名>通过 CMake 缓存覆盖(L4); - 若使用下载的引擎产物(而非本地构建),
include_directories(L24)指向的头文件路径也需要相应调整; - 别忘了
run.sh的--local-engine-src-path/--local-engine参数要与实际引擎位置一致。
7.2 画面缩放异常(像素比问题)
如果程序能跑但绘制比例不对,问题出在 FlutterEmbedderGLFW.cc 的g_pixelRatio计算,以及它对指针坐标(L31-L32)和窗口尺寸(L81-L82)的换算。在 HiDPI 屏幕上,GLFW 的窗口逻辑尺寸与帧缓冲物理尺寸不一致,若比例计算或上报不匹配,会出现 UI 过小/过大。可尝试:
- 检查
glfwGetFramebufferSize返回值是否符合预期; - 手工把
g_pixelRatio调整为固定值(如 1.0 或 2.0)验证是否为缩放问题; - 确认
GLFWwindowSizeCallback中的pixel_ratio字段上报正确。
7.3 GLFW 找不到
CMakeLists.txt 通过add_subdirectory直接编译仓库内的third_party/glfw,通常无需系统安装。若 CMake 仍找不到 GLFW,检查:
- 仓库内
third_party/glfw目录是否存在(示例依赖仓库自带的 GLFW 源码); include_directories(L17)的路径是否正确解析;- 也可改为
find_package(glfw3)并链接系统安装的 GLFW。
7.4 其他常见问题
- icudtl.dat 缺失:程序第二个参数必须指向有效的 ICU 数据文件,README 提示可在 Flutter SDK 的
bin/cache中找到; FlutterEngineRun返回非kSuccess:对照 FlutterEngineResult 枚举逐项排查(版本不匹配 →kInvalidLibraryVersion、参数错误 →kInvalidArguments);- 引擎 ABI 版本断言失败:编译期
static_assert(FLUTTER_ENGINE_VERSION == 1, ...)会拦截 API 大版本断裂(L18-L22),升级引擎时务必阅读 embedder.h 头部的 ABI 变更规则。
八、从示例到生产:进一步阅读
- 完整 Embedder API:单一 C 头文件 shell/platform/embedder/embedder.h(约 3500 行),涵盖渲染器、项目参数、平台消息、语义树、AOT 快照、自定义任务运行器等全部接口;头部注释(L12-L50)明确定义了 ABI 稳定性规则(结构体成员不可删除、新增成员必须追加在末尾、枚举值不可变动等),是嵌入器开发者的必读规范;
- 官方指导文档:Custom-Flutter-Engine-Embedders 说明了引擎动态库的获取方式(GN 目标
//shell/platform/embedder:flutter_engine需包含在 host 构建中;Mac / Linux / Windows 构建机都会上传对应产物,可将引擎 SHA 替换进下载 URL,该 SHA 可从 Flutter 框架仓库的bin/internal/engine.version获取); - 引擎 AOT 模式:仓库 docs/Flutter-engine-operation-in-AOT-Mode.md 讲解了快照数据/指令缓冲区的使用方式,对应
FlutterProjectArgs中的vm_snapshot_*与isolate_snapshot_*字段; - 多窗口与视图管理:示例固定使用
kImplicitViewId = 0的单窗口模型;embedder.h 中FlutterEngineAddView/FlutterEngineRemoveView(L2730-L2754 附近)则提供了多视图扩展能力; - 其他渲染后端:若目标平台不支持 OpenGL,可参考 FlutterRendererType 中的
kSoftware(软件渲染)、kMetal(Darwin)与kVulkan分支对应的渲染器配置结构。
结语
GLFW 示例虽然只有数百行代码,却浓缩了 Flutter Embedder 的全部核心契约:ABI 稳定的 C 接口、由嵌入器实现的渲染回调、事件上报通道,以及宿主与引擎之间的生命周期协调。以 run.sh + CMakeLists.txt 打通构建,以 FlutterEmbedderGLFW.cc 理解接入,以 embedder.h 扩展能力,你就能把 Flutter 渲染能力带到任何能提供窗口与 GL 上下文的宿主环境中。
- 跨平台
- 图形学
- 前端
【免费下载链接】engine
The Flutter engine
相关推荐
从零剖析 Flutter Engine Embedder 的 GLFW 桌面示例:原理、构建与运行
从零剖析 Flutter Engine Embedder 的 GLFW 桌面示例:原理、构建与运行 本指南以 Flutter 仓库中的 GLFW Embedde
跨平台移动开发前端UI组件桌面应用深入解析 Flutter Engine Embedder GLFW 脏区域渲染示例(glfw_drm)
深入解析 Flutter Engine Embedder GLFW 脏区域渲染示例(glfw_drm) Flutter 官方提供了一套针对 Android、iO
跨平台移动开发前端UI组件桌面应用Flutter 引擎嵌入器(Embedder API):为 Flutter 未开箱支持的平台编写自定义宿主
Flutter 引擎嵌入器(Embedder API):为 Flutter 未开箱支持的平台编写自定义宿主 本文基于仓库文档 Custom Flutter En
跨平台移动开发前端UI组件桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考