第6篇|页面加载白屏没报错:先查 WindowStage 和资源路径
第6篇|页面加载白屏没报错:先查 WindowStage 和资源路径
摘要:鸿蒙页面白屏时,最容易被误判成页面代码问题。实际排查下来,很多白屏发生在页面构建之前:Ability 创建了,WindowStage 也进来了,但 loadContent 的路径、时机或兜底没有写稳。我的习惯是先看窗口链路,再看页面状态,避免在组件里改半天却没碰到根因。
有一次我改完启动页后,真机打开应用只剩白屏。日志里能看到 onWindowStageCreate 进来了,页面里的 aboutToAppear 却没有任何输出。这个信号很关键:如果页面生命周期没有触发,说明问题大概率还在窗口加载阶段。
这篇文章会从工程链路讲清楚:
-
白屏先按哪几层拆。
-
WindowStage.loadContent应该怎样写才可追踪。 -
页面路径、模块归属和兜底页如何配合。
-
为什么不要一开始就在页面里加大量防御代码。


先判断白屏发生在哪一层
白屏不是一个原因,而是一类表现。排查时我会先分层:
| 层级 | 典型信号 | 优先看什么 |
|---|---|---|
| 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 里的条件分支、列表数据和异步状态。两个阶段不要混在一起修。
我通常会临时记录三类日志:
UIAbility入口日志,确认应用入口被系统拉起。WindowStage加载日志,确认窗口资源开始加载。- 页面生命周期日志,确认组件真的进入渲染链路。
这三类日志一旦能串起来,白屏位置就会清楚很多。修复完成后,保留必要的窗口失败日志,删掉页面里临时加的密集日志,避免后续交付时留下噪声。
拆模块后更要看资源归属
页面从 entry 移到 HSP/HAR 后,白屏不一定来自代码语法。页面路径、资源引用、模块依赖只要有一个没对齐,都可能让加载链路断掉。
我会重点复查:
- 页面文件是否还在
loadContent对应路径下。 - 页面依赖的资源是否随页面迁移。
build-profile.json5里模块依赖是否声明。- 页面导出或路由注册是否仍指向旧模块。
拆模块时不要只移动 .ets 文件。页面和资源是一组运行单元,路径也要一起更新。
我更倾向于按“页面包”来迁移:页面文件、页面专属组件、页面专属资源、页面路由常量一起迁移。比如详情页有自己的空态图、加载失败文案、播放器占位图,这些都应该跟详情页在同一个业务模块里。否则短期靠跨模块引用能跑,长期会让资源来源变得不可追踪。
迁移完成后,可以用下面这张表复查:
| 复查项 | 期望结果 |
|---|---|
| 页面路径 | loadContent 或路由表指向新路径 |
| 图片资源 | 页面引用的图片存在于当前模块资源目录 |
| 文案资源 | $r('app.string.xxx') 能在归属模块找到 |
| 依赖声明 | 宿主模块能依赖业务模块,业务模块不反向依赖宿主 |
| 兜底页 | 主页面失败时仍有简单页面可打开 |
这张表不复杂,但能把“移动了文件”变成“运行边界完成迁移”。
常见问题和处理方式
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| Ability 日志有,页面日志没有 | loadContent 路径错误 |
集中维护 PagePaths 并看回调 |
| 首页加载失败后一直白屏 | 没有兜底页 | 封装失败后加载 fallback |
| 拆模块后白屏 | 页面资源没有迁移 | 页面、资源、依赖一起复查 |
| 偶尔白屏 | 启动任务挡住窗口加载 | 先加载首屏,再补齐非关键任务 |
我的复查顺序
我通常按这条链路走:
- 看
UIAbility是否启动。 - 看
onWindowStageCreate是否进入。 - 看
loadContent回调是否成功。 - 看页面
aboutToAppear是否出现。 - 页面已进入后,再查状态、条件渲染和数据。
这条顺序能避免“页面还没加载,就在页面里修半天”的无效修改。
如果是首启白屏,我还会额外做两轮复查。第一轮清空应用数据后启动,确认默认状态下也能进入首页;第二轮从后台杀进程后重新打开,确认窗口链路不会依赖上一次残留状态。很多白屏只在冷启动出现,热启动时因为状态已经准备好,反而不容易暴露。
如果是改路由后白屏,我会从每个入口都走一次:桌面图标、首页按钮、通知入口、深层页面返回。只要其中一个入口能打开、另一个入口白屏,就说明问题不在页面本身,而在入口参数、路由常量或 Want 分发链路。
小结:白屏先看窗口,再看页面
白屏排查最重要的是顺序。先确认 Ability、WindowStage、loadContent、页面生命周期这条链路,再进入组件和业务状态。只要窗口加载有日志、路径有集中管理、失败有兜底页,白屏就不会变成一团猜测,而是能按阶段定位的工程问题。
更多推荐

所有评论(0)