应用启动问题经常被误判成“首屏页面写得慢”。真实链路里,用户点击图标后会依次经过 UIAbility 创建、数据初始化、WindowStage 创建、系统栏配置、首屏加载、页面状态恢复和启动参数派发。任何一步发生竞争,都可能表现为白屏、系统栏闪一下、统计数据先显示 0、点击桌面卡片只回首页,或者首次操作没有保存。

本文基于 化学实验室 1.0.5 的真实 ArkTS 源码,拆解 EntryAbility.ets、DataStore.ets、Index.ets 与 module.json5。当前入口采用 Stage 模型,onCreate() 锁定浅色模式并异步初始化 Preferences,onWindowStageCreate() 配置非沉浸窗口与系统栏,然后加载 pages/Index。整体方向清晰,但源码中存在三个可复现的启动风险。

第一,DataStore.init() 自己捕获所有异常并正常结束,因此调用端 .then() 在 Preferences 初始化失败时仍会记录“DataStore initialized”。第二,数据初始化与 loadContent() 没有统一状态,首屏可能在存储可用前触发读写;putNumber() 在实例为空时会直接返回,早期写入可能丢失。第三,EntryAbility 没有统一消费启动 Want,也没有热启动参数处理,桌面卡片等外部入口携带的目标会被忽略。

本文的核心原则是:启动不是一个回调,而是一组可以并行执行、必须显式汇合的就绪条件;窗口、首屏、数据和路由只有进入可解释状态,应用才算真正可交互。

本文唯一校验标记:CHEM-LAB-ONE12-STARTUP-READINESS-CHAIN-20260730

HarmonyOS 应用启动状态与首屏稳定性封面

本文会完成以下工程拆解:

  • 还原 EntryAbility 到 Index 的真实启动顺序。
  • 区分能力、窗口、首屏和前后台生命周期。
  • 修复“初始化失败仍记录成功”的假阳性日志。
  • 让存储初始化幂等、共享且可被页面等待。
  • 用首屏骨架兼顾启动速度与数据正确性。
  • 统一冷启动与热启动的 Want 解析和路由派发。
  • 建立启动时间线、故障矩阵和真机验收标准。

一、先画出当前真实启动时间线

当前代码可以还原为以下顺序:

EntryAbility.onCreate
  -> setColorMode(COLOR_MODE_LIGHT)
  -> DataStore.init(context)  // 异步启动,不等待

EntryAbility.onWindowStageCreate
  -> getMainWindowSync()
  -> setWindowLayoutFullScreen(false)
  -> 写入 statusBarHeight / bottomBarHeight = 0
  -> setWindowSystemBarProperties()  // Promise,不等待
  -> windowStage.loadContent('pages/Index')

DataStore.init()、系统栏属性设置和首屏加载可能重叠。并行本身不是错误,问题在于代码没有定义:哪一步失败可以降级,哪一步完成后页面才能允许持久化操作,待处理路由应该在哪个时刻派发。

二、Stage 模型中能力与窗口不是同一个生命周期

华为 Stage 模型把 UIAbility 与 WindowStage 分开:UIAbility 负责创建、销毁、前台和后台等组件状态,WindowStage 负责与显示相关的主窗口生命周期。当前项目把颜色模式和数据初始化放在 onCreate(),把主窗口与首屏放在 onWindowStageCreate(),符合职责方向。

但分开之后就必须处理汇合。onCreate() 启动的数据任务可能尚未完成,onWindowStageCreate() 已经把首屏加载出来;窗口也可能重建,而 UIAbility 实例仍存在。不能把“某个回调被调用”当作整个应用已就绪。

三、module.json5 决定启动页之前的第一帧

module.json5 为 EntryAbility 配置了:

{
  "name": "EntryAbility",
  "srcEntry": "./ets/entryability/EntryAbility.ets",
  "startWindowIcon": "$media:startIcon",
  "startWindowBackground": "$color:start_window_background",
  "exported": true
}

启动图标与启动背景是页面内容出现前的第一帧。它们应与 pages/Index 的背景和应用图标保持一致,否则即使首屏加载很快,用户仍会看到颜色跳变。启动稳定性不仅是代码耗时,也包括启动窗口、系统栏和首屏背景的视觉连续。

四、当前颜色策略是主动锁定浅色模式

onCreate() 调用了:

this.context.getApplicationContext().setColorMode(
  ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT
)

这意味着应用不跟随系统深色模式,而是明确使用浅色方案。若这是产品决定,应在主题资源、系统栏、卡片和发布检查中保持一致;若希望跟随系统,则不能只删除这一行,还要补齐深浅色资源和全部页面验证。

当前做法的风险不在“锁浅色”本身,而在配置分散:卡片 colorMode 为 auto,应用窗口却锁浅色。不同入口可能出现视觉策略不一致,应该在项目文档中明确统一选择。

五、系统栏配置与首屏加载目前是并行的

setWindowSystemBarProperties() 返回 Promise,当前代码通过 .catch() 处理失败,但随后立即调用 loadContent():

mainWindow.setWindowSystemBarProperties({
  statusBarColor: '#F5F7FA',
  statusBarContentColor: '#1A1A1A',
  navigationBarColor: '#FFFFFF',
  navigationBarContentColor: '#1A1A1A'
}).catch((err: Error) => {
  hilog.error(DOMAIN, 'One9App',
    'set system bar properties failed: %{public}s', err.message)
})

windowStage.loadContent('pages/Index', callback)

这能缩短阻塞,但在设备或窗口模式切换时可能先显示默认系统栏,再切换到目标颜色。是否需要等待取决于视觉要求和实际测试;至少应把系统栏状态纳入启动日志,并保证失败时文字和背景仍可读。

六、非沉浸窗口与安全区策略是一套协议

当前入口调用 setWindowLayoutFullScreen(false),同时把:

AppStorage.setOrCreate<number>('statusBarHeight', 0)
AppStorage.setOrCreate<number>('bottomBarHeight', 0)

写入全局。Index.ets 再用这两个值设置上下 padding。因为内容不进入系统栏区域,使用 0 是合理的;如果未来切换沉浸式布局,就不能只改 setWindowLayoutFullScreen(true),还必须重新计算避让区域和所有页面 padding。

这两处配置应视为同一个窗口协议,不能各自演进。

七、最严重的假阳性来自 DataStore.init

EntryAbility 以 Promise 成功与失败记录日志:

DataStore.init(this.context).then(() => {
  hilog.info(DOMAIN, 'One9App', 'DataStore initialized')
}).catch((err: Error) => {
  hilog.error(DOMAIN, 'One9App',
    'DataStore init failed: %{public}s', err.message)
})

但 DataStore.init() 内部已经吞掉异常:

static async init(context: common.UIAbilityContext): Promise<void> {
  try {
    DataStore.prefInstance = await preferences.getPreferences(context, PREF_NAME)
    await DataStore.refreshStatsSnapshot()
  } catch (_) {
    DataStore.prefInstance = null
  }
}

因此调用端的 .catch() 基本无法接到初始化失败,.then() 会继续输出成功。这会让排障人员相信存储可用,实际 prefInstance 已经是 null。

八、初始化结果要返回状态,而不是沉默

可以把初始化结果建模为明确状态:

export type StorageInitStatus = 'ready' | 'degraded'

export interface StorageInitResult {
  status: StorageInitStatus
  message: string
}

static async init(context: common.UIAbilityContext): Promise<StorageInitResult> {
  try {
    DataStore.prefInstance = await preferences.getPreferences(context, PREF_NAME)
    await DataStore.refreshStatsSnapshot()
    return { status: 'ready', message: 'preferences ready' }
  } catch (error) {
    DataStore.prefInstance = null
    const message = error instanceof Error ? error.message : 'unknown error'
    return { status: 'degraded', message }
  }
}

如果项目认为存储失败是不可恢复错误,也可以直接抛出;关键是调用方必须得到真实结论,不能用成功 Promise 表示失败。

九、初始化任务必须幂等并共享

窗口重建、测试入口或其他服务可能重复调用初始化。建议保存正在进行的 Promise:

private static initTask?: Promise<StorageInitResult>

static ensureInitialized(
  context: common.UIAbilityContext
): Promise<StorageInitResult> {
  if (DataStore.initTask !== undefined) {
    return DataStore.initTask
  }
  DataStore.initTask = DataStore.init(context)
  return DataStore.initTask
}

这样所有调用者等待同一任务,不会并发打开多个 Preferences 实例,也不会重复刷新全局统计快照。

十、首屏抢跑会造成什么真实后果

loadContent() 不等待 DataStore,页面可能先触发读取。读取方法在 prefInstance 为空时返回默认值,因此统计先显示 0;初始化完成后 refreshStatsSnapshot() 会写入 AppStorage,部分组件能响应更新。

更危险的是写入路径:

static async putNumber(key: string, value: number): Promise<void> {
  if (!DataStore.prefInstance) return
  // 后续才写 Preferences
}

如果用户在初始化窗口内完成一次计数操作,该写入会直接消失。putString() 虽会更新内存快照,但同样不会持久化。启动竞态因此不是理论问题,而是可能形成“第一次操作没保存”。

十一、不要用延迟重试掩盖初始化竞态

一种常见补丁是在页面里 setTimeout(500) 后再读写。这依赖设备性能和系统负载,无法证明 500ms 后存储一定就绪。

正确做法是等待共享初始化任务:

const initResult = await DataStore.ensureInitialized(context)
if (initResult.status !== 'ready') {
  return { ok: false, message: '本地数据暂不可用' }
}
await DataStore.putNumber('experiment_count', nextCount)

时间不是状态;只有初始化 Promise 的结果才是状态。

十二、首屏速度与数据正确性不必二选一

不建议为了等存储而让整个屏幕保持启动窗口。更好的策略是分层渲染:

  1. WindowStage 建立后尽快加载稳定的首屏骨架。
  2. 首屏展示导航和不依赖存储的静态内容。
  3. 统计、收藏和记录区域进入 loading 状态。
  4. 数据初始化完成后切换为 content。
  5. 初始化失败时显示 degraded 与重试入口。

用户能快速看到可识别界面,同时不会把默认 0 当作真实数据。

十三、定义统一启动状态快照

可以把启动阶段建模为一个只读快照:

export type ReadyState = 'pending' | 'ready' | 'failed'

export interface StartupSnapshot {
  window: ReadyState
  systemBars: ReadyState
  content: ReadyState
  storage: ReadyState
  route: ReadyState
  message: string
}

页面和日志都从这份快照读取,不再用几条互不相关的“成功”日志猜启动是否完成。

十四、启动协调器只负责汇合,不承担业务

export class StartupCoordinator {
  private snapshot: StartupSnapshot = {
    window: 'pending',
    systemBars: 'pending',
    content: 'pending',
    storage: 'pending',
    route: 'pending',
    message: 'starting'
  }

  markStorage(state: ReadyState): void {
    this.snapshot = { ...this.snapshot, storage: state }
    this.tryDispatchRoute()
  }

  markContent(state: ReadyState): void {
    this.snapshot = { ...this.snapshot, content: state }
    this.tryDispatchRoute()
  }
}

协调器不读取题目、不修改收藏,也不操作页面组件;它只汇合启动条件并通知路由层。

EntryAbility 从接收 Want 到首屏可交互的启动流程

十五、窗口配置失败应允许受控降级

系统栏颜色设置失败通常不必阻止首屏加载,但必须保证默认窗口仍可读。可以把窗口配置拆成结果:

interface WindowSetupResult {
  layoutReady: boolean
  systemBarsReady: boolean
  message: string
}

layoutReady=false 才阻止内容;systemBarsReady=false 可以继续加载并记录降级。这样错误处理与影响范围匹配。

十六、loadContent 失败不能只写日志

当前回调在 err.code 非零时记录后返回,用户可能留在启动窗口或空白窗口。至少需要定义可观察回退:

windowStage.loadContent('pages/Index', (error) => {
  if (error.code) {
    this.startupCoordinator.markContent('failed')
    this.loadFallbackContent(windowStage)
    return
  }
  this.startupCoordinator.markContent('ready')
})

回退页应尽量少依赖业务模块,显示“启动失败、重新尝试、退出”即可。若当前版本不实现二级回退页,也要在测试中把首屏加载失败列为发布阻断项。

十七、启动 Want 应在 onCreate 第一时间规范化

EntryAbility 的 onCreate(want, launchParam) 当前没有使用两个参数。来自桌面卡片、通知或应用链接的目标因此丢失。

可以只提取白名单字段:

export interface LaunchRequest {
  source: string
  target: string
  taskId?: string
}

export class LaunchRequestParser {
  static parse(want: Want): LaunchRequest | undefined {
    const source = String(want.parameters?.['source'] ?? '')
    const target = String(want.parameters?.['target'] ?? '')
    if (source.length === 0 || target.length === 0) return undefined
    return { source, target }
  }
}

不要把完整 want.parameters 直接传播到页面,避免未知字段和旧版本参数影响路由。

十八、冷启动参数不能在首屏未就绪时立即跳转

如果在 onCreate() 里直接调用页面路由,ArkUI 内容尚未加载。更稳的方式是先保存待处理请求:

private pendingLaunch?: LaunchRequest

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  this.pendingLaunch = LaunchRequestParser.parse(want)
  this.startDataBootstrap()
}

当 content=ready 且目标依赖的数据也准备好后,再交给 LaunchRouter。请求只消费一次,并在成功或回退后清空。

十九、热启动需要独立入口但共享解析器

应用已存在时再次收到启动请求,不会重新走完整冷启动。入口需要按当前 SDK 的 UIAbility 生命周期实现热启动 Want 处理,并复用同一个 LaunchRequestParser:

onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  const request = LaunchRequestParser.parse(want)
  this.startupCoordinator.enqueueRoute(request)
}

具体签名以项目目标 SDK 的类型定义为准。工程约束是冷启动和热启动不能各写一套参数规则。

二十、Index 页当前总是从首页标签开始

Index.ets 定义:

@State currentIndex: number = 0
private tabController: TabsController = new TabsController()

学习标签索引是 2,但没有外部启动请求入口。卡片点击即使拉起 EntryAbility,也会看到首页。可以通过受控 AppStorage 或页面级路由协调器传入请求标签,避免 EntryAbility 直接操作组件实例。

二十一、路由恢复要区分标签与二级页面

启动目标可能只是“打开学习标签”,也可能是“进入每日一题”。可分两步:

LaunchRouter
  -> resolve target
  -> request root tab = 2
  -> wait Index ready
  -> push views/learning/DailyQuizPage

若二级页面失败,保留在学习标签;若 target 未知,也回退学习标签。服务直达失败不应导致白屏。

二十二、数据初始化失败时哪些功能还能用

启动降级必须有边界。可以继续使用纯静态知识浏览和公式查看,但收藏、笔记、学习统计和实验记录应禁用写入并提示“本地数据暂不可用”。

不要让 putNumber() 静默返回后仍显示“保存成功”。服务方法需要把写入结果返回页面:

export interface WriteResult {
  ok: boolean
  message: string
}

二十三、DataStore 的错误吞噬会破坏可观测性

当前多数方法都使用空 catch。这能避免页面崩溃,却让磁盘、格式和上下文错误全部消失。建议至少在仓库边界记录受控错误,并让关键写操作返回失败。

“稳定”不是永远不抛异常,而是错误被分类、用户获得可恢复路径、日志能解释发生了什么。

二十四、AppStorage 快照需要版本语义

DataStore 通过 stats_version 通知统计变化,这是一个可用方向。启动时可以明确:

AppStorage.setOrCreate<'pending' | 'ready' | 'degraded'>(
  'dataBootstrapState',
  'pending'
)

初始化完成后更新状态与快照版本。页面只有在 ready 时把数字解释为真实统计,在 pending 时显示加载态,在 degraded 时显示失败态。

二十五、重复初始化与重试需要定义规则

共享 initTask 后,还要决定失败能否重试。可以在 degraded 后清除任务,允许用户主动重试;成功后长期复用:

if (result.status === 'degraded') {
  DataStore.initTask = undefined
}

自动无限重试会放大启动耗时和日志,建议由用户操作或明确前后台事件触发,并限制频率。

二十六、启动日志应该是一条时间线

当前日志标签统一为 One9App,可以增加启动会话 ID 和阶段:

startup=170001 phase=ability_create result=ok
startup=170001 phase=storage result=ready costMs=18
startup=170001 phase=window result=ready costMs=4
startup=170001 phase=content result=ready costMs=42
startup=170001 phase=route result=fallback target=daily_quiz

数值只在真实运行时采集,文章不预设性能结论。日志应使用相对阶段耗时,避免只看一条“启动成功”。

二十七、不要把完整异常对象公开输出

当前部分日志使用 JSON.stringify(err) 与 %{public}s。启动 Want 和系统错误可能包含不适合公开的字段。建议只输出错误码、受控消息和阶段,不记录用户内容、完整参数或文件路径。

二十八、职责拆分后的启动结构

EntryAbility
  -> WindowSetup
  -> DataBootstrap
  -> ContentLoader
  -> LaunchRequestParser
  -> StartupCoordinator
      -> StartupSnapshot
      -> LaunchRouter

EntryAbility 负责按生命周期调用,不直接处理统计、收藏或题目。窗口配置、存储初始化、内容加载和路由解析都可独立测试。

窗口配置、数据初始化、内容加载和启动路由的职责结构

二十九、启动状态机的合法转换

每个阶段只允许:

pending -> ready
pending -> failed
failed -> pending  // 用户重试

已经 ready 的存储不应因页面重建回到 pending;新 WindowStage 则可以拥有新的窗口状态。状态归属必须和生命周期一致。

三十、测试一:存储成功与失败分支

使用适配器替换 Preferences 获取逻辑,固定返回成功或抛错:

interface PreferencesFactory {
  open(context: common.UIAbilityContext): Promise<preferences.Preferences>
}

断言成功时状态为 ready,失败时为 degraded,调用端绝不能记录“initialized”。

三十一、测试二:首屏先于存储完成

构造一个延迟存储 Promise:

  1. 先标记 content ready。
  2. 页面统计区域应保持 loading。
  3. 尝试写入时返回“数据准备中”,不能静默成功。
  4. 释放 Promise 后显示真实快照。
  5. 待处理路由仅在依赖满足后派发。

这个用例直接覆盖真实启动竞态。

三十二、测试三:窗口配置失败

模拟系统栏 Promise 拒绝,验证内容仍能加载、默认系统栏可读、启动状态记录为降级。再模拟 loadContent 失败,验证它被视为发布阻断,而不是只写日志继续。

三十三、测试四:冷启动与热启动路由

场景输入预期
普通冷启动无 target进入首页
卡片冷启动target=daily_quiz数据就绪后进入每日一题
卡片热启动应用在实验室标签切换学习标签并进入目标页
未知目标target=unknown回退学习标签
重复请求同一 taskId 连续两次只保留一个目标页
数据降级目标依赖存储显示错误并可重试

三十四、测试五:多窗口与重建

项目声明支持 phone、tablet 和 2in1。要验证 WindowStage 重建后:系统栏重新应用,Index 状态可恢复,DataStore 不重复打开,待处理路由不重复消费,窗口尺寸变化不造成首屏重叠。

三十五、启动性能指标要分阶段采集

至少记录:

  • Ability 创建开始。
  • WindowStage 创建。
  • loadContent 回调成功。
  • 首屏骨架可见。
  • 数据快照 ready。
  • 首次可交互。
  • 目标路由完成。

只有拆开指标,才能判断慢在窗口、页面构建、存储还是路由,而不是笼统地优化 EntryAbility。

三十六、常见故障一:统计先显示 0 后跳变

这是首屏把默认值当内容渲染。让页面订阅 dataBootstrapState,pending 时显示骨架,ready 后再显示数字。不要用动画掩盖数据语义错误。

三十七、常见故障二:第一次操作没有保存

检查操作是否发生在 prefInstance 为空期间,以及 putNumber() 是否提前返回。关键写入必须等待 ensureInitialized() 并把失败返回页面。

三十八、常见故障三:日志显示成功但数据为空

检查初始化函数是否内部吞掉异常。当前源码正是这种情况:init() catch 后正常 resolve。改为返回结构化结果或抛出错误。

三十九、常见故障四:系统栏启动时闪变

比较启动窗口背景、默认系统栏、目标系统栏与首屏背景。记录属性设置 Promise 完成时间;若视觉要求严格,再决定是否等待系统栏配置后加载内容。

四十、常见故障五:卡片点击只回首页

确认 onCreate 和热启动入口都读取 Want,首屏 ready 后才派发,并让 Index 接收目标标签。目标页失败时回退学习标签。

四十一、真机发布前启动验收

  1. 冷启动连续执行 10 次,无白屏、闪退和无响应。
  2. 清除进程后从桌面卡片启动,能进入目标页面。
  3. 应用前台、后台各执行一次热启动目标。
  4. Preferences 正常时统计与收藏恢复正确。
  5. 模拟数据异常时页面进入降级态,不伪报成功。
  6. 深色系统环境下锁浅色策略仍保持系统栏可读。
  7. 手机、平板、2in1 窗口变化后内容不重叠。
  8. 安装、启动、核心流程、退出和卸载完整通过。

四十二、分阶段落地策略

第一阶段先修改 DataStore.init() 的结果语义并补测试。第二阶段增加 dataBootstrapState,修复首屏默认值和早期写入。第三阶段引入 StartupCoordinator,统一窗口、内容和数据状态。第四阶段打通冷、热启动 Want。第五阶段补启动时间线、多窗口和发布回归。

每一阶段都对应一个可观察结果,避免一次性重写入口后难以定位回归。

四十三、完成标准

  • 存储失败不会记录成功。
  • 首屏加载快,但依赖数据的区域不会展示假内容。
  • 初始化期间的写操作不会静默丢失。
  • 系统栏失败有降级,首屏失败有明确回退或阻断。
  • 冷启动和热启动共用 Want 解析与路由规则。
  • 窗口重建不会重复初始化或重复跳转。
  • 日志能还原一条完整启动时间线。

四十四、官方资料与版本核对

本文以项目真实源码为分析对象。UIAbility 生命周期、窗口 API 与主题接口应按目标 SDK 继续核对:

生命周期签名、系统栏属性和启动参数能力可能随 API 版本演进,代码落地前应以 DevEco Studio 当前类型提示与真机行为为准。

四十五、源码事实与建议实现的边界

下面将 EntryAbility.ets 与 DataStore.ets 中可以直接复核的启动行为,与本文建议的协调结构分开,避免把架构方案误写成已经完成的修复。

观察维度当前源码事实建议实现
能力模型EntryAbility 继承 UIAbility,使用 Stage 模型生命周期保留系统入口,在能力层引入可观测的启动协调器
主题设置onCreate 尝试把应用色彩模式固定为浅色,失败只记录日志明确主题策略与降级状态,并验证系统深色模式下首帧一致性
数据初始化onCreate 触发 DataStore.init(this.context),但不等待其完成再加载首屏将初始化任务做成幂等共享 Promise,页面按依赖选择等待或降级
错误语义DataStore.init 捕获异常后把实例置空并正常返回,调用端 .then() 仍会记录初始化成功让初始化结果显式表达 ready/degraded/failed,禁止成功日志假阳性
早期读写putNumber 在 Preferences 实例为空时直接返回;getNumber 返回默认值对关键写入排队或显式失败,读取区分真实默认值与存储尚未就绪
窗口配置onWindowStageCreate 设置非全屏、系统栏颜色,并把安全区高度写入 AppStorage把窗口、系统栏和内容加载状态写入统一启动快照
首屏加载窗口创建后固定调用 loadContent('pages/Index')允许快速渲染稳定骨架,同时让依赖数据的区域显示加载或降级状态
启动参数onCreate 接收 Want,当前没有消费外部入口目标使用白名单解析器把冷启动参数转换为一次性导航意图
热启动当前入口没有统一的 onNewWant 参数消费路径以目标 SDK 的准确签名补热启动处理,并与冷启动共用解析规则
生命周期日志现有代码记录创建、窗口、前后台与销毁日志增加同一启动 ID、阶段耗时和结构化失败原因,避免日志无法串联

本文提出的 StartupCoordinator、启动快照、共享初始化任务、写入队列、首屏骨架、Want 白名单解析器和冷/热启动统一路由,都是建议实现,当前仓库尚未集成。本次精修没有执行 DevEco Studio 构建、安装、冷启动、热启动、窗口重建或故障注入测试,因此不能据此声称白屏、早期写入丢失和外部入口跳转问题已经修复;落地后仍需以真实构建与设备时间线验证。

总结

化学实验室的 EntryAbility 已具备 Stage 模型入口、窗口配置、首屏加载和本地存储初始化的基础链路。真正影响稳定性的不是回调数量,而是这些回调之间没有共享“是否就绪”的事实:DataStore 失败仍表现为成功,首屏可能在存储前抢跑,系统栏与内容并行但不可观测,启动 Want 也没有进入页面路由。

把窗口、系统栏、内容、存储和路由建模成结构化启动快照,让初始化任务幂等共享、错误不再吞掉、首屏采用加载与降级状态、冷启动与热启动共用同一解析器,应用才能从“多数时候能打开”升级为“每次启动都能解释、验证和恢复”。

AI 辅助声明:本文由 AI 辅助完成源码梳理、结构校对与配图设计,技术结论均以文中列出的本地 ArkTS 源码为依据,并由作者复核后发布。

Logo

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

更多推荐