【化学实验室|13】HarmonyOS ArkTS 应用启动链路实战:从 EntryAbility 到首屏加载保持窗口与路由稳定
应用启动问题经常被误判成“首屏页面写得慢”。真实链路里,用户点击图标后会依次经过 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

本文会完成以下工程拆解:
- 还原
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 的结果才是状态。
十二、首屏速度与数据正确性不必二选一
不建议为了等存储而让整个屏幕保持启动窗口。更好的策略是分层渲染:
- WindowStage 建立后尽快加载稳定的首屏骨架。
- 首屏展示导航和不依赖存储的静态内容。
- 统计、收藏和记录区域进入
loading状态。 - 数据初始化完成后切换为
content。 - 初始化失败时显示
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()
}
}
协调器不读取题目、不修改收藏,也不操作页面组件;它只汇合启动条件并通知路由层。

十五、窗口配置失败应允许受控降级
系统栏颜色设置失败通常不必阻止首屏加载,但必须保证默认窗口仍可读。可以把窗口配置拆成结果:
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:
- 先标记 content ready。
- 页面统计区域应保持 loading。
- 尝试写入时返回“数据准备中”,不能静默成功。
- 释放 Promise 后显示真实快照。
- 待处理路由仅在依赖满足后派发。
这个用例直接覆盖真实启动竞态。
三十二、测试三:窗口配置失败
模拟系统栏 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 接收目标标签。目标页失败时回退学习标签。
四十一、真机发布前启动验收
- 冷启动连续执行 10 次,无白屏、闪退和无响应。
- 清除进程后从桌面卡片启动,能进入目标页面。
- 应用前台、后台各执行一次热启动目标。
- Preferences 正常时统计与收藏恢复正确。
- 模拟数据异常时页面进入降级态,不伪报成功。
- 深色系统环境下锁浅色策略仍保持系统栏可读。
- 手机、平板、2in1 窗口变化后内容不重叠。
- 安装、启动、核心流程、退出和卸载完整通过。
四十二、分阶段落地策略
第一阶段先修改 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 源码为依据,并由作者复核后发布。
更多推荐



所有评论(0)