启动性能与首帧优化

一、引言

启动是用户对应用的第一印象:冷启动超过 3 秒,用户就可能放弃等待;首帧长时间白屏,再好的内容也无人问津。多设备短视频项目横跨直板机、PC、TV、手表四个 HAP,不同设备的硬件差异决定了启动策略必须"一机一策"——手表不能照搬 PC 的启动逻辑,TV 的焦点系统也需要在首帧前就绪。

从第 46 篇的总览视角看,启动性能属于"卡顿"维度的特例:它不是单帧问题,而是从进程创建、Ability 生命周期、首帧渲染到数据就绪的整条链路问题。本章围绕项目四个 HAP 的入口 MultiShortVideo*Ability.ets,拆解启动链路、冷启动优化、首帧路径精简、懒初始化与按需加载,并对比四个 HAP 的启动差异。

breakpoint-system

二、启动链路分析:Ability 生命周期与 loadContent

冷启动的完整链路如下:

graph LR
    A[点击图标] --> B[进程创建]
    B --> C[onCreate]
    C --> D[onWindowStageCreate]
    D --> E[loadContent 首帧]
    E --> F[onForeground]
    F --> G[数据加载/播放器初始化]

onWindowStageCreate 是首帧前最关键的一环。loadContent 之前的所有同步代码都会推迟首帧,因此这里只做"必须做的事"。以直板机入口为例(products/default/src/main/ets/defaultability/MultiShortVideoDefaultAbility.ets):

onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('view/Index', (err) => {
    if (err.code) { ...; return; }
    let windowUtil = WindowUtil.getInstance();
    windowUtil.setWindowStage(windowStage);
    windowUtil.setUIContext();
    windowUtil.updateWindowInfo();
    // 设置状态栏文字颜色等窗口属性
  });
}

注意两点:窗口初始化放在 loadContent 成功回调之后,避免阻塞首帧;WindowUtil 是单例懒创建,首次调用才实例化。如果反过来先做窗口配置再 loadContent,首帧时间会被无谓拉长。

启动按进程与页面状态可分为三类,优化目标不同:

类型定义优化重点

冷启动进程不存在,全量创建缩短能力创建与首帧路径
温启动进程在但页面销毁复用进程与单例,快速重建页面
热启动进程与页面均在后台恢复前台,几乎无开销

onCreate 只执行一次,冷启动的主要耗时集中在 onWindowStageCreateloadContent;温启动则依赖 WindowUtil 等单例跨生命周期存活,避免重复初始化。项目四个 HAP 的 onWindowStageDestroy 都调用 WindowUtil.getInstance().release() 注销监听,但单例对象本身保留,保证下一次启动能复用窗口信息。

三、Ability 冷启动优化:按需窗口配置

不同设备的窗口配置差异明显,也决定了启动路径的长短:

HAP入口 Ability启动窗口额外配置

直板机MultiShortVideoDefaultAbility状态栏文字颜色
PCMultiShortVideoPcAbility装饰栏隐藏、高度、按钮样式
TVMultiShortVideoTvAbility无(依赖焦点系统)
手表MultiShortVideoWearableAbility无(精简)

PC 端的配置最多,但都发生在 loadContent 回调中,且 setWindowDecorVisiblesetWindowDecorHeightsetDecorButtonStyle 相互独立、互不等待,全部走异步接口,不阻塞首帧:

// products/pc/src/main/ets/pcability/MultiShortVideoPcAbility.ets(节选)
windowStage?.getMainWindowSync().setWindowDecorVisible(false);
windowStage?.getMainWindowSync().setWindowDecorHeight(56);
windowStage?.getMainWindowSync().setDecorButtonStyle({ colorMode: ... });

冷启动优化的三条原则:

  • onCreate 保持极简:只做参数解析与必要单例,不初始化任何 UI 相关资源;
  • 窗口配置后置:跟随 loadContent 回调,与首帧渲染并行;
  • 不阻塞回调链:多个窗口接口串行 await 会拖慢可用时间,改为并发触发。

四、首帧渲染路径精简

首帧时间 = 加载页面 + 构建首屏组件树 + 首帧上屏。缩短路径从页面结构入手,项目 Index.ets 的首页结构为:

// products/default/src/main/ets/view/Index.ets(节选)
build() {
  Navigation(this.pathStack) {
    MSVTabs({
      data: this.data,
      isDark: this.isDark,
      onIndexChange: (index: number) => { ... }
    })
  }
  .navBarWidth(new WidthBreakpointType<number>(410, 410, 700, 700).getValue(this.windowInfo.widthBp))
  .hideBackButton(true)
  .hideTitleBar(true)
  .mode(this.showSideComment || this.showSideIndividual ? NavigationMode.Split : NavigationMode.Stack)
}

精简手法:

  • 数据量最小化getMainTabsData() 只构造 5 个页签模型,页签内容通过 @BuilderBuilders.ets 中的 home() 等)延迟到 TabContent 构建时才执行;
  • 惰性页签内容:非首页签(朋友、消息、我的)对应 MSVEmptyComponent 空态,几乎零成本;视频流 recommend() 在用户切到推荐页签时才构建 AdaptiveVideoForDefault,播放器初始化被推迟到真正需要时;
  • 先框架后细节NavPathStackNavBarWidth 等骨架属性先行,侧栏、模式切换等状态按需响应。

五、懒初始化与按需加载

首帧之后仍有大量"重活"可以后置,避免挤占首帧预算:

资源初始化时机触发点

WindowUtil 单例首次调用loadContent 回调
主 Tabs 数据aboutToAppearIndex 构建前
视频数据源aboutToAppearAdaptiveVideo 构建
播放器实例成为当前页onLoad + currentIndex
评论数据打开评论时Comment aboutToAppear
作品数据进入个人页时Works aboutToAppear

数据模型层均为"构造即就绪"的轻量对象:MainTabsViewModel.getMainTabsData() 在内存中构建页签数组,AvDataSourceViewModel.getAvDataSource() 返回 5 条本地视频路径,均无网络与磁盘等待。播放器的 initAVPlayerisInitializing 标志与 onLoad 双重保护,只初始化一次且只在可见时执行,这是首帧后最大的启动成本控制点(详见第 47、49 篇)。

六、四个 HAP 的启动差异对比

多 HAP 架构下,各设备的启动策略对照如下:

维度直板机PCTV手表

首页复杂度高(Tabs+视频流)高(分栏+Tabs)高(Tabs+焦点)低(精简 Tabs)
首帧负担视频流懒构建分栏布局焦点系统初始化组件极少
主要优化点播放器后置窗口配置并发焦点即时响应资源最小化
数据准备内存模型内存模型内存模型内存模型

手表端首页只加载精简的 SubTabsComponent 与空态页签,不引入视频流;TV 端在首帧前不额外做窗口配置,把资源留给焦点系统;PC 端并发执行多项窗口配置但全部异步。四者共享同一套 WindowUtil 单例与数据模型,启动代码的差异化被收敛到 Ability 层——这正是"公共代码下沉 common、平台差异隔离在 products"架构的收益。

七、总结与最佳实践

  • 启动链路按"onCreate 极简 → loadContent 先行 → 窗口配置后置 → 数据懒加载"的顺序编排;
  • 冷启动优化优先保证首帧时间:任何非必要同步工作都不要放在 onWindowStageCreate 的同步段;
  • 首帧路径精简靠惰性构建:页签内容 @Builder 延迟执行、空态页签零成本、播放器不可见不初始化;
  • 懒初始化用单例与标志位保证"只做一次":WindowUtil.getInstance()isInitializing 防重入;
  • 多设备启动策略差异化:手表减组件、TV 保焦点、PC 并发窗口配置,但共享公共代码;
  • 启动性能同样要量化:用 Profiler 的启动分析(Launch)面板测量冷启动与首帧时间,建立回归基线。
Logo

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

更多推荐