鸿蒙 Flutter 启动原理与闪屏白层问题总结
1.背景与现象
当鸿蒙 Flutter 工程以 debug 模式运行时(release 通常没那么明显),冷启动常见体验是:
- 先出现系统闪屏(StartWindow:图标 + 纯色背景)
- 中间短暂出现一层白屏 / 闪一下
- 再到 Flutter 的 Splash 页
核心问题:中间这一层白屏是怎么来的?要回答这个问题,需要先理解鸿蒙 Flutter 的启动原理,以及系统 StartWindow 与 Flutter 首帧之间的时间空隙。
2.鸿蒙 Flutter 启动原理
鸿蒙启动 Flutter 的核心可以概括为:在保留 Flutter Framework 与 Engine Core 主体不变的前提下,为 OpenHarmony / HarmonyOS 重新实现了一套 Embedder(嵌入层)。Embedder 相当于适配器:把 Flutter 引擎指令翻译成鸿蒙可理解的窗口、生命周期、输入与渲染行为。
2.1三层架构与关键组件

2.2 关键组件
Android VSync 信号的监听及传递
在 Flutter 引擎的 Android 实现中,设备的 VSync 信号通过 Choreographer 触发,它产生及消费流程如下图所示:
Flutter 在Android:

Flutter 的经典分层架构在鸿蒙上依然适用,关键在于最底层的 Embedder 层被完全替换为鸿蒙版本。
-
Framework 层 (Dart):你的业务代码和 Flutter 组件,完全复用。
-
Engine 层 (C++):渲染引擎(Skia/Impeller)、Dart 虚拟机等,核心逻辑复用,但增加了平台相关的 Shell 实现。
-
Embedder 层 (平台相关):这是鸿蒙适配的核心。它使用 C++ 编写(如 platform_view_ohos.cpp),负责与鸿蒙系统底层 API 交互,创建窗口、管理生命周期、处理输入事件等。
围绕这个核心,鸿蒙侧有几个关键组件协同工作:
-
FlutterAbility:鸿蒙应用的入口,继承自 UIAbility。它负责在 onCreate 等生命周期回调中,触发 Flutter 引擎的初始化。
-
FlutterAbilityAndEntryDelegate:一个协调者,串联起 Ability 生命周期、引擎创建、视图创建和 Dart 启动的整个流程。
-
OHOSShellHolder:负责创建和管理 Flutter 引擎的核心 Shell 对象,管理渲染循环和事件处理。
-
PlatformViewOHOS:继承自 Flutter 通用的 PlatformView 接口,持有鸿蒙的图形上下文(OHOSContext)和渲染表面(OHOSSurface)
2.3 与 Android 的核心区别
| 维度 | Android | 鸿蒙 |
| 渲染合成 | Surface / SurfaceFlinger | RSSurface / Rosen |
| 平台通道 | JNI → Java/Kotlin | NAPI → ArkTS(缺 ohos 实现时易 MissingPluginException) |
| 生命周期入口 | Activity onCreate / onStart 等 | Ability + onWindowStageCreate(更接近 UI 构建时机) |
| 原生启动闪屏 | Theme / LaunchTheme + launch_background,通常可盖到 Flutter 首帧 | StartWindow(icon + 纯色背景),能力更弱;收起后若无原生过渡层,容易露底 |
| flutter_native_splash | 成熟支持 | 本工程未覆盖 OHOS;需手写 ArkUI 过渡层 |
一句话对比:Android 更擅长「原生启动图一直顶到 Flutter 首帧」;鸿蒙 StartWindow 收起更早,中间空窗期必须由业务自己补(窗口背景色 + splashScreenView)。
补充:Android Embedder 通过 Choreographer 监听并传递 VSync;鸿蒙侧由对应平台 Shell / Rosen 链路驱动帧调度。该差异解释「怎么画」,不是本次白屏的直接原因,白屏主因仍是 StartWindow 收起后的空窗期。
3. 本工程冷启动时序

用户点击图标
↓
[1] 系统 StartWindow
startWindowIcon = $$media:icon
startWindowBackground = $$color:start_window_background
↓
[2] EntryAbility.onCreate
FlutterAbility 创建 Delegate、初始化 FlutterEngine
configureFlutterEngine(注册插件)
↓
[3] EntryAbility.onWindowStageCreate
设置主窗口背景色(与 start_window_background 一致)
创建 FlutterView
windowStage.loadContent('pages/Index')
(Dart 入口多在此前后触发执行)
↓
[4] Index.ets 渲染
Navigation / Column 使用同色背景
FlutterPage.splashScreenView = FlutterSplashBuilder
(纯色底 + 居中 icon,对齐系统 StartWindow)
↓
[5] XComponent 绑定渲染面 → Dart main() 继续执行
若 main() 中 await 重量级 _init(),会拉长过渡层停留时间
↓
[6] runApp → Flutter Splash / 首页首帧
FlutterPage.onFirstFrame → 移除 splashScreenView
中间「白层 / 闪一下」主要发生在 [1] → [4] 的交接缝,以及 debug 下 XComponent 默认白底叠加时。
[1]:

[2]:EntryAbility.onCreate
[3]:EntryAbility.onWindowStageCreate

[4] FlutterSplashBuilder

[5] flutter的main启动初始化

[6] runApp → Flutter Splash / 首页

4. 白屏 / 闪一下的根因
4.1 主因:StartWindow 收起后的空窗期
系统 StartWindow 在 loadContent 完成后会收起。此时若:
-
主窗口背景仍是默认色(常偏白)
-
ArkUI 尚未完成首帧绘制
-
Flutter 引擎尚未画出第一帧
用户就会看到短暂白层。这与 App 深浅色主题无直接关系:空窗期不走 Flutter 主题。
4.2 Debug 加重:XComponent 强制白底
flutter_ohos 在 debug 模式下对 TEXTURE 类型 XComponent 写死:
oh_modules/.ohpm/@ohos+flutter_ohos@hpm+99bfbn08xndcfeq0gxszwl63jtxynhvkfjhju0=/oh_modules/@ohos/flutter_ohos/src/main/ets/component/XComponentStruct.ets
build() {
// todo OS解决默认背景色后可以移除冗余重复代码,仅保留差异的backgroundColor属性条件配置
if (this.applicationInfo.isDebugMode) {
XComponent({
id: (this.dvModelParams as Record<string, Any>)["xComponentId"],
type: XComponentType.TEXTURE,
libraryname: 'flutter'
})
.onLoad((context) => {
this.context = context;
})
.onDestroy(() => {
})
.backgroundColor(Color.White)
} else {
XComponent({
id: (this.dvModelParams as Record<string, Any>)["xComponentId"],
type: XComponentType.TEXTURE,
libraryname: 'flutter'
})
.onLoad((context) => {
this.context = context;
})
.onDestroy(() => {
})
}
}
.backgroundColor(Color.White)
因此即便系统深色 StartWindow 是黑底,debug 交接时仍可能闪白。Release 没有这层强制白底,所以现象会减轻——与观测一致。
源码下如图:

4.3 资源不一致会放大「跳变感」
若过渡层使用 launcher_background / launcher_foreground,而系统 StartWindow 使用 icon + start_window_background,则背景色与图标构图都不一致,用户更容易感知为「闪一下 / 换了一层」。
4.4 图标异步加载
过渡层 Image 若异步解码,会出现:先出纯色底 → 再弹出 icon,二次跳变。
4.5 系统图标大小不可配置
StartWindow 图标尺寸由系统规范决定,业务侧无法设置;ArkUI 过渡层必须自己写 width/height,只能目测对齐,无法 100% 读取系统值。
5. 解决方案(本工程落地)
原则:让「系统 StartWindow → 原生过渡层 → Flutter 首帧」视觉连续:同色、同图、尽早钉死窗口背景,并同步加载图标。
5.1 与系统 StartWindow 对齐的原生过渡层
新增独立文件,便于维护:
ohos/entry/src/main/ets/components/FlutterSplashBuilder.ets
• 背景:$color:start_window_background(与 module.json5 一致)
• 图标:$media:icon(与 startWindowIcon 同一张)
• 图标同步加载:.syncLoad(true)
• 尺寸常量:FLUTTER_SPLASH_ICON_SIZE(当前 158,可按机型目测微调)
Index.ets 中接入:
FlutterPage({ viewId: this.viewId, splashScreenView: FlutterSplashBuilder, })
并为 Navigation / Column 设置同样的 start_window_background,避免容器默认浅色透底。
5.2 窗口背景提前钉死
在 EntryAbility.onWindowStageCreate 中,调用 super(内部 loadContent)之前:
windowStage.getMainWindowSync() .setWindowBackgroundColor(与 start_window_background 相同的色值)
这样 StartWindow 收起后的空窗期不再露出默认白底。
5.3 颜色资源配置
| 资源 | 浅色 | 深色 |
| start_window_background | #FFFFFF | #000000 |
深色用纯黑,可与 icon.png 自带黑底更好衔接,减少「黑图标方块周围一圈异色」。

5.4 关于 Flutter Splash
-
短期:保留 Flutter Splash 业务逻辑;原生过渡层负责盖住引擎启动空窗期。
6. 为什么 Debug 仍可能略闪,但可接受
-
debug XComponent 白底是 flutter_ohos 实现细节,业务层难以彻底删除
-
只要 splashScreenView 全屏不透明且同步出图,多数情况下可盖住
-
验收以 release 为准;debug 用于开发,允许轻微差异
7. 验证建议
-
冷启动:系统闪屏 → 过渡层 → Flutter,中间不应再出现明显白屏
-
分别验证浅色 / 深色系统主题
-
对比 debug / release:release 应更干净
-
检查图标大小是否接近系统 StartWindow(必要时只改 FLUTTER_SPLASH_ICON_SIZE)
8. 涉及文件清单
-
涉及文件清单
| 文件 | 作用 |
| ohos/entry/src/main/module.json5 | startWindowIcon / startWindowBackground |
| ohos/entry/src/main/resources/*/element/color.json | start_window_background 深浅色 |
| ohos/entry/src/main/resources/base/media/icon.png | 系统闪屏与过渡层共用图标 |
| ohos/entry/src/main/ets/components/FlutterSplashBuilder.ets | 原生过渡层(独立维护) |
| ohos/entry/src/main/ets/pages/Index.ets | 挂载 splashScreenView / 容器同色底 |
| ohos/entry/src/main/ets/entryability/EntryAbility.ets | loadContent 前 setWindowBackgroundColor |
| lib/app/modules/splash/splash.view.dart | Flutter Splash(业务页,未强行改成原生同款) |
9. 结论
鸿蒙 Flutter 冷启动白层,本质上不是 Flutter Splash“画错了”,而是:
-
1系统 StartWindow 能力弱于 Android LaunchTheme
-
StartWindow 收起到 Flutter 首帧之间存在空窗期
-
debug 下 XComponent 白底放大了该问题
解决路径不是“在 Flutter 里再画一遍启动图”那么简单,而是:
用与系统 StartWindow 同色同图的 ArkUI 过渡层 + 提前设置窗口背景色,把空窗期填平。
本人在初稿「启动原理」基础上,补充了本工程现象复盘、根因分层与落地改动说明。
更多推荐


所有评论(0)