本来以为 3.44 至少会发布 win 的多窗口结果只宣布了个Canonical 成为 Flutter Desktop 的主要维护者和战略合作伙伴实际上很久前 Flutter 就把桌面端交给对方维护这两年也基本是这么在推进只是速度确实太慢这次更新里主要提到了showRawDialog/showDialog走原生 dialog window 如果你flutter config --enable-windowing开启了 windowing那么 dialog 会通过 windowing system 显示在自己的窗口而不是当前窗口内的 modal overlay平台不支持时会 fallback 到普通 dialog route。这个问题倒是把我坑了因为我一直用的 beta 版本这次用了多窗口之后很多 loading 和弹窗直接去到了新窗口上然后又没适配直接 UX 都乱了。另外目前在接口层面大部分 win 接口已经可用了RegularWindowController/RegularWindowDialogWindowController/DialogWindowTooltipWindowController/TooltipWindowPopupWindowController/PopupWindowSatelliteWindowController/SatelliteWindow不过多窗口在 Flutter 里确实挺多边界问题因为框架一开始就没设计多窗口概念所以很多东西都是在重构现在的多窗口的实现是一个 Flutter engine 实例能够渲染多个独立的操作系统窗口而这些窗口共享同一个 Dart isolate 和 widget tree然后通过 view ID 区分不同的渲染目标所以多窗口之间的竞态细节会比较多。不过问题更多还是在于平台适配从零开始做一套脱离平台渲染的多窗口还真的难度不小。Win根据我体验下来win 11 上基本都还行用起来问题不大就是多窗口焦点切换还有点交互支持不够友好但是在 win 10 上目前问题应该比较多 比如在一些 Win10 上开关窗口、激活、最大化这些基础功能在多窗口都还有问题主要是WM_SIZE 与 等待机制在 Win10 上的行为不一致。目前 Flutter Win embedder 处理窗口大小变化包括最大化、多窗口创建/关闭时的尺寸变化的核心逻辑是窗口收到WM_SIZE消息resize、maximize 等触发embedder 的平台线程在flutter_windows_view.cc中执行一个condition_variablemutex超时是 100ms持续等待直到 Flutter engine 产出一帧与新目标尺寸匹配的帧等待释放后新帧才真正呈现到屏幕这个等待机制是 Flutter Win 主要是为了防止出现画面撕裂和黑屏闪烁的而问题就在于这个 Win10 在某些窗口操作尤其是最大化以及多窗口场景下的创建/激活/关闭时WM_SIZE消息时序导致等待提前超时或条件变量通知时序错乱跟 Win11 又居然不一样这就导致等待的触发路径跟不上了。另外 Win 10 和 Win11 在窗口合成也有差异DWMDesktop Window Manager行为差异Win10/Win11 的 DWM、窗口合成策略、驱动模型和窗口样式存在差异Win 11 的窗口合成会更激进处理中间帧窗口在内容好之前不会 破门而出 显示给用户Win 10 的 DWM 对窗口显示的同步更宽松更容易暴露出黑帧WS_EX_LAYERED与WM_NCCALCSIZE处理差异Flutter Win 创建窗口时使用的窗口样式在 Win 10 和 11 上对WM_NCCALCSIZE的处理有行为差异会影响 resize 时的中间状态是否可见当然更大问题其实是多窗口场景放大了这些问题单窗口时应用启动已经走了一遍这个路径即便有 bug 也只暴露一次多窗口时每次新建/激活/关闭窗口都会触发上述路径Win 10 上每次操作都可能走到有问题的分支另外还可能存在点击问题居然会有在第一帧 layout 完成之前就被分发进来的点击事件这些问题都是相当细节的具体场景目前我整体体验 win11 还行就是焦点切换还不太友好。macOS另外 macOS 上也是类似主要问题还是窗口在第一帧渲染完成之前就显示了而且问题在 Intel MacmacOS 15.7.5上可以稳定复现在 M2 MacBook PromacOS 26.4上又不稳定复现不得不说现在 macOS 的版本号碎片化也很丰富了。那为什么这个问题在 macOS 上也会因为 macOS 的窗口显示机制macOS 使用NSWindow和NSView窗口创建后如果调用makeKeyAndOrderFront:窗口就会立即可见Flutter macOS embedder 目前没有实现 延迟显示直到第一帧就绪 的机制而RegularWindowController目前又缺少隐藏/延迟创建能力创建窗口但不立即显示等待就绪信号每窗口的 首帧已呈现 信号没有一个回调或事件能告知上层 这个窗口的第一帧已渲染完毕创建时的初始背景色窗口在内容就绪前没有正确的背景颜色导致显示黑色或透明状态所以这就导致了 macOS 上窗口时序没办法严格遵循而实际上这个问题在单窗口场景下就一直有很久之前 #554272020 年提的 就有类似 Consider hiding windows until the engine is active in the desktop runner templates.这上面 macOS 的问题一直都还没被修复因为系统机制原因Windows 的修复思路很直接在 runner 模板里创建窗口时加WS_VISIBLE为 false然后等 engine 的 first-frame callback 触发后再ShowWindow因为 Win32 API 设计上支持这种模式callback 机制也在 engine 里已经存在。而 macOS 就遭罪了AppKit 的orderFront:和makeKeyAndOrderFront:是立即生效的没有 deferred 参数。也就是根据需求需要在 embedder 层向 engine 注册 per-view 的 first-frame 回调而这个机制在 macOS embedder 没可用实现目前 macOS embedder 的 first-frame 通知是 engine 级别的不是 view 级别的多窗口下无法精确知道 哪个 view 的第一帧已就绪。当然在 Tooltip/Popup 场景下会好一点因为用了alphaValue 0.0 positioner 回调来延迟显示但是也是治标不治本这也是 macOS 目前最大痛点。Linux而 Linux 就更糟心了相信用 Linux 的应该都懂这就不是人力问题而是GTK3 的 OpenGL 上下文架构从设计上就不支持多个 GtkGLArea 共享同一个 GL 上下文这导致多 view 渲染在 Linux 上面临需要绕过 GTK 底层限制的工程问题感受一下时间事件2023 年 11 月issue #138178 由 dkwingsmt 创建标注 mostly for tracking purposes无人认领2024 年 7 月engine PR #54018 作为第一步合入仅是基础结构调整2024 年 10 月prototype 可用正在拆分为可提交的 PR同时揭示了核心技术问题GTK3 GL 上下文限制2024 年 10 月engine PR #55541view ID 分配、#55542view ID 释放合入这是仅有的两个实质进展2025 年 6 月robert-ancell 提交 draft PR #170045尝试用 EGL 绕过 GTK3 限制但标注 not working on X112025 年 7 月PR #170045被关闭未合入X11 fallback 未完成2026 年 1 月bot 自动提醒 assignee 没有进展robert-ancell 回复 Still chipping away at this仍在做2026 年 3 月issue #183911 创建指出 Linux embedder 多 view 下 shader 需要共享是又一个新的 P2 子问题2026 年 5 月bot 再次提醒无进展assignee 被系统自动移除issue 目前无人认领GTK3 的GtkGLArea每个实例都有独立的GdkGLContext这些上下文之间默认不能共享 texture、framebuffer 等 GL 资源。在单窗口场景下Flutter engine 把帧渲染到一个GdkGLContext里就结束了但是啊多窗口下 engine 需要把不同 view 的帧分别 present 到对应的GtkGLArea而各自的 GL 上下文是隔离的engine 没有办法直接把一个 view 的渲染结果跨上下文传递给另一个 window。而 prototype 采用的绕过方案在 implicit view第一个窗口的 GL 上下文里渲染所有内容然后通过 CPU 回读再写入其他窗口的上下文但是这一听就知道多不靠谱 CPU readback 本身就是 GPU pipeline 的性能杀手。另外还有提到用独立的 EGL 上下文替代 GdkGLContextEGLImage是一种可以在 EGL 上下文之间共享 texture 的机制不需要 CPU 拷贝。但是但是但是这个方案在 X11 上不工作啊X11 和 Wayland 在 EGL 的实现细节上有差异EGLImage在 X11 上的驱动支持没有在 Wayland 上那么普遍而且 X11 的GdkGLContext是基于 GLX 而不是 EGL 的和 EGL-based 的 Flutter context 之间需要额外的 interop这部分还没有实现所以路子又窄了。这也是 Linux 的另一个独特麻烦需要同时需要支持 X11 和 Wayland 两套显示协议而这两者在底层 GL/EGL 栈上的行为有明显差异Wayland 上 EGL 是原生的EGLImage扩展支持普遍EGL context 和 GTK4/Wayland surface 的集成有官方文档X11 上 GTK 传统上使用 GLX不是 EGL与 EGL-based 的 Flutter rendering context 之间需要额外的互操作层没有干净的官方路径即便强制在 X11 上用 EGL通过EGL_EXT_platform_x11扩展的驱动支持也不如 Wayland 普遍开源驱动Mesa和私有驱动NVIDIA行为不一致这也是为什么很多对支持 Linux 不热衷的原因。最后不管怎么说多窗口确实推进的有些久了Canonical 的投入也一直不瘟不火感觉 AI 时代了在不加速推进 release 感觉多窗口就要烂裤兜里了不过目前至少我在 win11 场景上还行。