第6篇|页面加载白屏没报错:先查 WindowStage 和资源路径

摘要:鸿蒙页面白屏时,最容易被误判成页面代码问题。实际排查下来,很多白屏发生在页面构建之前:Ability 创建了,WindowStage 也进来了,但 loadContent 的路径、时机或兜底没有写稳。我的习惯是先看窗口链路,再看页面状态,避免在组件里改半天却没碰到根因。

有一次我改完启动页后,真机打开应用只剩白屏。日志里能看到 onWindowStageCreate 进来了,页面里的 aboutToAppear 却没有任何输出。这个信号很关键:如果页面生命周期没有触发,说明问题大概率还在窗口加载阶段。
在这里插入图片描述

这篇文章会从工程链路讲清楚:

  1. 白屏先按哪几层拆。

  2. WindowStage.loadContent 应该怎样写才可追踪。

  3. 页面路径、模块归属和兜底页如何配合。

  4. 为什么不要一开始就在页面里加大量防御代码。

    在这里插入图片描述

在这里插入图片描述

先判断白屏发生在哪一层

白屏不是一个原因,而是一类表现。排查时我会先分层:

层级 典型信号 优先看什么
Ability 未启动 入口日志没有出现 module.json5、启动 Ability
WindowStage 异常 进入 Ability 但无窗口内容 onWindowStageCreate
页面未加载 loadContent 后页面无生命周期 页面路径和资源归属
页面渲染失败 页面生命周期出现但内容不显示 状态、条件渲染、数据

这一步非常重要。只有先确定白屏发生在页面前还是页面内,后面的修改才不会跑偏。

给窗口链路加清晰日志

窗口链路的日志不需要多,但要能串起来。比如:

// entryability/EntryAbility.ets
onWindowStageCreate(windowStage: window.WindowStage): void {
  console.info('[window] stage created')

  windowStage.loadContent('pages/HomePage', (err) => {
    if (err.code) {
      console.error(`[window] load HomePage failed: ${JSON.stringify(err)}`)
      return
    }
    console.info('[window] HomePage loaded')
  })
}

这段代码能帮你确认两件事:WindowStage 是否真的创建;loadContent 的回调是否返回错误。如果只写一行 loadContent,白屏时就很难判断页面是否已经进入。

页面路径不要靠记忆维护

页面路径写错是白屏常见原因,尤其是页面移动、模块拆分、目录重命名之后。更稳的做法是集中维护入口路径:

// common/navigation/PagePaths.ets
export const PagePaths = {
  home: 'pages/HomePage',
  startupFallback: 'pages/StartupFallbackPage',
  courseDetail: 'pages/CourseDetailPage'
} as const

然后窗口加载只引用常量:

// entryability/EntryAbility.ets
import { PagePaths } from '../common/navigation/PagePaths'

onWindowStageCreate(windowStage: window.WindowStage): void {
  this.loadMainContent(windowStage, PagePaths.home)
}

集中路径不是为了形式统一,而是为了减少“目录改了一个地方,入口还留着旧路径”的问题。多模块项目尤其需要这一层。

封装一个可兜底的 loadMainContent

真正交付时,首页加载失败不能只留白屏。可以封装一个很轻的加载函数:

// entryability/EntryAbility.ets
private loadMainContent(windowStage: window.WindowStage, pagePath: string): void {
  console.info(`[window] loading ${pagePath}`)

  windowStage.loadContent(pagePath, (err) => {
    if (!err.code) {
      console.info(`[window] loaded ${pagePath}`)
      return
    }

    console.error(`[window] load ${pagePath} failed: ${JSON.stringify(err)}`)
    windowStage.loadContent(PagePaths.startupFallback)
  })
}

这里的重点是“失败有去处”。即使首页路径或资源出了问题,用户也不应该只看到空白;开发者也能通过日志知道失败发生在窗口加载阶段。

兜底页要足够简单

兜底页不要依赖复杂业务状态,否则它自己也可能加载失败。它应该只展示一个可理解的错误状态和一个重新进入按钮。

// pages/StartupFallbackPage.ets
@Entry
@Component
struct StartupFallbackPage {
  build() {
    Column({ space: 16 }) {
      Text('页面加载失败,请重新进入')
        .fontSize(20)
        .fontWeight(FontWeight.Medium)

      Button('返回首页')
        .onClick(() => {
          router.replaceUrl({ url: 'pages/HomePage' })
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

这类页面的原则是少依赖、少状态、少条件渲染。它不是用来承载业务的,只是让失败状态可见,避免白屏无声发生。

如果页面生命周期没出现,就别先改组件

很多白屏排查会浪费在组件层,比如给 build 加判断、给列表加空态、给图片加默认值。但如果页面的 aboutToAppear 没出现,这些修改都不会生效。

我会在页面入口加一个短日志:

// pages/HomePage.ets
aboutToAppear(): void {
  console.info('[page] HomePage aboutToAppear')
}

如果窗口日志显示 loadContent 失败,或者页面日志没出现,就回到路径、资源、模块归属。只有页面生命周期已经触发,才继续看业务状态和条件渲染。

这里还有一个很实用的判断:如果 loadContent 回调成功,但页面日志没有出现,就要看页面入口是否被条件编译、导出边界或模块配置影响;如果页面日志出现了,但首屏仍是空白,再去查 build 里的条件分支、列表数据和异步状态。两个阶段不要混在一起修。

我通常会临时记录三类日志:

  1. UIAbility 入口日志,确认应用入口被系统拉起。
  2. WindowStage 加载日志,确认窗口资源开始加载。
  3. 页面生命周期日志,确认组件真的进入渲染链路。

这三类日志一旦能串起来,白屏位置就会清楚很多。修复完成后,保留必要的窗口失败日志,删掉页面里临时加的密集日志,避免后续交付时留下噪声。

拆模块后更要看资源归属

页面从 entry 移到 HSP/HAR 后,白屏不一定来自代码语法。页面路径、资源引用、模块依赖只要有一个没对齐,都可能让加载链路断掉。

我会重点复查:

  1. 页面文件是否还在 loadContent 对应路径下。
  2. 页面依赖的资源是否随页面迁移。
  3. build-profile.json5 里模块依赖是否声明。
  4. 页面导出或路由注册是否仍指向旧模块。

拆模块时不要只移动 .ets 文件。页面和资源是一组运行单元,路径也要一起更新。

我更倾向于按“页面包”来迁移:页面文件、页面专属组件、页面专属资源、页面路由常量一起迁移。比如详情页有自己的空态图、加载失败文案、播放器占位图,这些都应该跟详情页在同一个业务模块里。否则短期靠跨模块引用能跑,长期会让资源来源变得不可追踪。

迁移完成后,可以用下面这张表复查:

复查项 期望结果
页面路径 loadContent 或路由表指向新路径
图片资源 页面引用的图片存在于当前模块资源目录
文案资源 $r('app.string.xxx') 能在归属模块找到
依赖声明 宿主模块能依赖业务模块,业务模块不反向依赖宿主
兜底页 主页面失败时仍有简单页面可打开

这张表不复杂,但能把“移动了文件”变成“运行边界完成迁移”。

常见问题和处理方式

现象 常见原因 处理方式
Ability 日志有,页面日志没有 loadContent 路径错误 集中维护 PagePaths 并看回调
首页加载失败后一直白屏 没有兜底页 封装失败后加载 fallback
拆模块后白屏 页面资源没有迁移 页面、资源、依赖一起复查
偶尔白屏 启动任务挡住窗口加载 先加载首屏,再补齐非关键任务

我的复查顺序

我通常按这条链路走:

  1. UIAbility 是否启动。
  2. onWindowStageCreate 是否进入。
  3. loadContent 回调是否成功。
  4. 看页面 aboutToAppear 是否出现。
  5. 页面已进入后,再查状态、条件渲染和数据。

这条顺序能避免“页面还没加载,就在页面里修半天”的无效修改。

如果是首启白屏,我还会额外做两轮复查。第一轮清空应用数据后启动,确认默认状态下也能进入首页;第二轮从后台杀进程后重新打开,确认窗口链路不会依赖上一次残留状态。很多白屏只在冷启动出现,热启动时因为状态已经准备好,反而不容易暴露。

如果是改路由后白屏,我会从每个入口都走一次:桌面图标、首页按钮、通知入口、深层页面返回。只要其中一个入口能打开、另一个入口白屏,就说明问题不在页面本身,而在入口参数、路由常量或 Want 分发链路。

小结:白屏先看窗口,再看页面

白屏排查最重要的是顺序。先确认 Ability、WindowStage、loadContent、页面生命周期这条链路,再进入组件和业务状态。只要窗口加载有日志、路径有集中管理、失败有兜底页,白屏就不会变成一团猜测,而是能按阶段定位的工程问题。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐