欢迎光临
我们一直在努力

34-首次启动异常白屏-用启动账本和恢复页兜底

第34篇|首次启动异常白屏:用启动账本和恢复页兜底

摘要:首次启动异常白屏,最怕没有页面、没有错误、没有日志。很多项目把配置读取、用户恢复、缓存迁移、远程开关都塞进首屏前,一步失败就停在白屏。更稳的做法是 WindowStage 先加载安全壳页面,启动账本记录阶段,必需任务短超时执行,失败时进入恢复页,非必需任务放到首屏后补。

我遇到过一个新安装首次启动白屏:开发机复现不了,用户清数据后必现。后来发现启动页等待本地配置迁移和远程开关,两者任何一个失败都没有兜底页面。白屏不是一个页面问题,而是启动链路没有安全出口。

在这里插入图片描述

这篇文章解决四个实际问题:

  • 如何让窗口先加载可见安全壳。
  • 如何记录启动阶段,避免白屏无上下文。
  • 如何区分首屏必需任务和首屏后任务。
  • 如何验证清数据、迁移失败和重试路径。
  • 在这里插入图片描述
    在这里插入图片描述

    先把首屏前任务减到最少

    任务是否首屏前必需失败处理
    加载本地默认配置 失败进入恢复页
    用户会话恢复 可降级 首屏后补或进入未登录态
    远程开关 使用本地默认值
    缓存预热 首屏后后台执行
    日志初始化 是但要轻量 失败不阻断页面

    WindowStage 先加载安全壳

    export class EntryAbility extends UIAbility {
    onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/BootShellPage')
    }
    }

    安全壳页面要足够简单,不依赖复杂业务数据。它负责显示启动中、恢复入口或跳转主页面。

    启动账本记录阶段

    export interface BootStepRecord {
    name: string
    startedAt: number
    finishedAt: number
    success: boolean
    message: string
    }

    export class BootLedger {
    private records: BootStepRecord[] = []

    finish(name: string, startedAt: number, success: boolean, message: string): void {
    this.records.push({ name, startedAt, finishedAt: Date.now(), success, message })
    }
    }

    白屏排查最缺上下文。账本至少要知道卡在哪一步、耗时多久、失败原因是什么。

    必需任务短超时执行

    export interface StartupTask {
    name: string
    required: boolean
    run(): Promise<void>
    }

    export class StartupService {
    constructor(private readonly ledger: BootLedger) {}

    async runRequiredTasks(tasks: StartupTask[]): Promise<boolean> {
    for (const task of tasks.filter(item => item.required)) {
    const startedAt = Date.now()
    try {
    await task.run()
    this.ledger.finish(task.name, startedAt, true, '')
    } catch (err) {
    this.ledger.finish(task.name, startedAt, false, '启动任务失败')
    return false
    }
    }
    return true
    }
    }

    首屏前任务失败要有明确结果,不要让 Promise 悬挂在启动页里。

    恢复页给重试和清理入口

    @Component
    struct BootRecoveryView {
    @State message: string = '启动失败,请重试'
    retryAction?: () => void
    clearAction?: () => void

    build() {
    Column({ space: 12 }) {
    Text(this.message)
    Button('重试启动').onClick(() => this.retryAction?.())
    Button('清理本地缓存后重试').onClick(() => this.clearAction?.())
    }
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
    }
    }

    恢复页不是装饰,它给用户和排障人员一个明确出口。

    首屏后任务不要阻断可用性

    export class AfterFirstFrameTasks {
    async run(tasks: StartupTask[]): Promise<void> {
    for (const task of tasks.filter(item => !item.required)) {
    try {
    await task.run()
    } catch (err) {
    // 记录即可,不阻断主页面
    }
    }
    }
    }

    远程开关、缓存预热、诊断上报都可以首屏后补。用户先看到可用页面,比卡在启动页更重要。

    验证清单

  • 清空数据后首次启动,能看到安全壳或首页,不白屏。
  • 模拟本地配置损坏,进入恢复页。
  • 模拟远程开关失败,使用默认值进入首页。
  • 启动日志能看到每个阶段耗时。
  • 重试启动后能从恢复页回到正常首页。
  • 非必需任务失败不会阻断首屏。
  • 启动失败要区分可恢复和不可恢复

    恢复页也不能只有一个“重试”。不同失败需要不同出口。

    失败类型用户入口
    配置读取失败 重试、清缓存
    数据迁移失败 查看修复提示、备份后重试
    远程开关失败 使用默认配置进入
    账号恢复失败 进入未登录态

    如果所有失败都只给“重试”,用户可能一直卡在同一个坏状态里。恢复页要把失败类型转成可执行动作。

    启动账本要能落到日志里

    export class BootLedgerReporter {
    report(records: BootStepRecord[]): void {
    records.forEach((item: BootStepRecord) => {
    const cost = item.finishedAt item.startedAt
    console.info(`[boot] ${item.name} success=${item.success} cost=${cost} message=${item.message}`)
    })
    }
    }

    发布态不需要输出大量 debug,但启动失败时至少要有阶段、耗时和结果。否则白屏问题只能靠猜。

    安全壳页面要少依赖资源和业务

    BootShellPage 的目标是“最坏情况下也能显示”。它不应该依赖远程配置、复杂主题、用户资料或业务列表。图片也不要放太重,避免资源读取异常时连恢复页都显示不出来。最稳的安全壳只需要标题、状态文案、重试按钮和清理入口。等必需任务完成后,再进入真正首页。

    安全壳自检可以按这三项做:

  • 清空数据后仍能显示文字和按钮。
  • 远程配置失败时不影响恢复页渲染。
  • 主题或图片资源异常时仍能进入最小可用页面。
  • 启动任务要有超时边界

    有些白屏不是抛错,而是 Promise 一直不返回。必需任务也要有超时。

    export async function withTimeout(task: Promise<void>, timeoutMs: number): Promise<void> {
    return new Promise<void>((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error('startup task timeout')), timeoutMs)
    task.then(() => {
    clearTimeout(timer)
    resolve()
    }).catch((err) => {
    clearTimeout(timer)
    reject(err)
    })
    })
    }

    超时后进入恢复页,比一直停在启动动画里更容易排查。超时时间不要随便设很长,首屏前任务应该短而确定。

    首次启动回归表

  • 新安装首次启动。
  • 清空数据后首次启动。
  • 本地配置文件损坏。
  • 账号信息不存在。
  • 远程开关超时。
  • 缓存迁移抛错。
  • 重试后恢复正常。
  • 这些路径都要能看到页面和日志。只验证“正常启动一次”无法证明白屏问题已经修好。

    交付前验收表

    验收项通过标准
    安全壳 WindowStage 后先显示可见页面
    必需任务失败 进入恢复页,不白屏
    非必需任务失败 不阻断首页
    启动日志 有阶段、耗时和失败原因

    启动类问题要用清数据、新安装、异常迁移三类路径验证。只跑正常启动一次没有意义。

    白屏修复后要保留失败样本

    启动白屏最难的是复现。修复后建议保留三类失败样本:损坏配置、迁移抛错、远程配置超时。每类样本都应该能稳定进入恢复页,并在日志里看到对应阶段。这样下次启动链路再改时,可以把这些样本当回归用例,而不是重新等用户反馈。

    boot regression samples:
    – broken_local_config -> RecoveryPage
    – migration_throw_error -> RecoveryPage with repair message
    – remote_flag_timeout -> HomePage with default flags

    这些样本不需要进入发布包,但要在开发或测试说明里留下构造方式。否则“白屏已修复”很容易退化成只验证了一次正常启动。

    小结:首屏必须有可见出口

    首次启动白屏要从启动链路修。WindowStage 先加载安全壳,启动账本记录阶段,必需任务短链路执行,失败进入恢复页,非必需任务首屏后补。这样即使失败,也不是无上下文白屏。
    pair message

    • remote_flag_timeout -> HomePage with default flags

    这些样本不需要进入发布包,但要在开发或测试说明里留下构造方式。否则“白屏已修复”很容易退化成只验证了一次正常启动。
    ## 小结:首屏必须有可见出口

    首次启动白屏要从启动链路修。WindowStage 先加载安全壳,启动账本记录阶段,必需任务短链路执行,失败进入恢复页,非必需任务首屏后补。这样即使失败,也不是无上下文白屏。

    赞(0)
    未经允许不得转载:171主机测评 » 34-首次启动异常白屏-用启动账本和恢复页兜底
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址