1.背景与现象

  当鸿蒙 Flutter 工程以 debug 模式运行时(release 通常没那么明显),冷启动常见体验是:

  1.    先出现系统闪屏(StartWindow:图标 + 纯色背景)
  2.    中间短暂出现一层白屏 / 闪一下
  3.    再到 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 / SurfaceFlingerRSSurface / Rosen
平台通道JNI → Java/KotlinNAPI → 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

  1. 短期:保留 Flutter Splash 业务逻辑;原生过渡层负责盖住引擎启动空窗期。

6. 为什么 Debug 仍可能略闪,但可接受

  1. debug XComponent 白底是 flutter_ohos 实现细节,业务层难以彻底删除

  2. 只要 splashScreenView 全屏不透明且同步出图,多数情况下可盖住

  3. 验收以 release 为准;debug 用于开发,允许轻微差异

7. 验证建议

  1. 冷启动:系统闪屏 → 过渡层 → Flutter,中间不应再出现明显白屏

  2. 分别验证浅色 / 深色系统主题

  3. 对比 debug / release:release 应更干净

  4. 检查图标大小是否接近系统 StartWindow(必要时只改 FLUTTER_SPLASH_ICON_SIZE)

8. 涉及文件清单

  1. 涉及文件清单

文件作用
ohos/entry/src/main/module.json5startWindowIcon / startWindowBackground
ohos/entry/src/main/resources/*/element/color.jsonstart_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.etsloadContent 前 setWindowBackgroundColor
lib/app/modules/splash/splash.view.dartFlutter Splash(业务页,未强行改成原生同款)

9. 结论

鸿蒙 Flutter 冷启动白层,本质上不是 Flutter Splash“画错了”,而是:

  1. 1系统 StartWindow 能力弱于 Android LaunchTheme

  2. StartWindow 收起到 Flutter 首帧之间存在空窗期

  3. debug 下 XComponent 白底放大了该问题

解决路径不是“在 Flutter 里再画一遍启动图”那么简单,而是:

用与系统 StartWindow 同色同图的 ArkUI 过渡层 + 提前设置窗口背景色,把空窗期填平。

本人在初稿「启动原理」基础上,补充了本工程现象复盘、根因分层与落地改动说明。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐