答案之书-01-启动白屏先别改页面:把题库播种放进 WindowStage 之前

发布时间:2026/9/13 9:03:14

答案之书-01-启动白屏先别改页面:把题库播种放进 WindowStage 之前 答案之书 序列1启动白屏先别改页面把题库播种放进 WindowStage 之前这类问题在轻量工具类应用里很常见页面看起来只有几个按钮但一旦把离线题库、动画、收藏、导入和发布状态串起来真正容易出错的不是某个组件而是数据从哪里来、什么时候可读、谁负责写入、页面靠什么刷新。The_Book_of_Answers这个项目的价值在于它没有把答案之书写成一次性的页面 Demo而是把主入口、HAR 公共层、HSP 功能层、Preferences 持久化、Navigation 路由和资源归属拆开。下面的写法都来自这个项目的真实工程链路读者可以把它当成一个小型 HarmonyOS 离线应用的拆解样本。这篇文章解决四件事把项目里的真实代码链路拆成可复用的工程方法。说明页面、服务、仓库、资源或发布配置之间的责任边界。给出可以迁移到其他 HarmonyOS 项目的 ArkTS / JSON5 写法。留下验证路径和排障表避免只看源码不看运行结果。1. 问题从哪里冒出来首页依赖当前题库题库选择器也要在页面出现时读取列表。如果loadContent先执行页面可能先拿到空索引后续即使种子数据写进 Preferences也不一定能触发所有页面重算。这个项目把首屏稳定性放到EntryAbility.onWindowStageCreate里处理先让仓库和默认题库就绪再挂载pages/Index。asynconWindowStageCreate(windowStage:window.WindowStage):Promisevoid{constctxthis.context;try{awaitPreferencesStore.init(ctx);awaitSeedLoader.run(ctx);}catch(err){hilog.error(DOMAIN,testTag,bootstrap failed: %{public}s,(errasError).message);}windowStage.loadContent(pages/Index,(err){if(err.code){hilog.error(DOMAIN,testTag,Failed to load the content. Cause: %{public}s,JSON.stringify(err));return;}});}这段代码背后的工程意图是把职责停在正确层级页面只处理交互和展示服务层处理业务规则仓库层处理持久化细节配置文件说明运行时归属。只要这个边界不乱后续增加页面、换资源或做发布自查时就不会牵一发动全身。2. 仓库初始化要硬失败项目没有让未初始化的 Preferences 静默返回空值。静默回退最危险因为上层会误以为“用户没有题库”再触发错误的空状态或重复播种。更稳的做法是启动期必须打开 store读取期如果仓库没准备好就直接抛错让问题停在启动链路。asyncinit(context:common.UIAbilityContext):Promisevoid{if(this.inited){return;}this.contextcontext;awaitthis.openStore(PrefStoreName.App);awaitthis.openStore(PrefStoreName.Deck);awaitthis.openStore(PrefStoreName.Favorite);this.initedtrue;}这段代码背后的工程意图是把职责停在正确层级页面只处理交互和展示服务层处理业务规则仓库层处理持久化细节配置文件说明运行时归属。只要这个边界不乱后续增加页面、换资源或做发布自查时就不会牵一发动全身。3. 默认题库不是只在首启写一次离线应用经常会升级内置内容。如果只判断deck_index是否为空老用户永远拿不到新的默认答案如果每次都覆盖又会破坏用户修改过的题库信息。项目用LastSeededVersion、默认题库缺失、默认题库损坏三个条件做分支既能升级种子又能保留用户对默认题库的名称和颜色调整。constids:string[]awaitDeckRepository.loadIndex();constdefaultDeck:Deck|nullawaitDeckRepository.loadDeck(DEFAULT_DECK_ID);constseededVersion:numberawaitPreferencesStore.getNumber(PrefStoreName.App,AppPrefKey.LastSeededVersion,0);constneedsSeed:booleanids.length0||defaultDecknull||(defaultDeck.answers.lengthSEED_RESTORE_THRESHOLD)||seededVersionDEFAULT_DECK_SEED_VERSION;这段代码背后的工程意图是把职责停在正确层级页面只处理交互和展示服务层处理业务规则仓库层处理持久化细节配置文件说明运行时归属。只要这个边界不乱后续增加页面、换资源或做发布自查时就不会牵一发动全身。4. rawfile 到对象要先清洗默认题库来自entry/src/main/resources/rawfile/seed/default_deck.json。读取后不要直接保存至少要做 trim、去空、补 id 和时间戳。这样后续随机抽取、收藏来源和编辑页 key 都能依赖稳定字段而不是靠文本本身充当身份。constraw:Uint8Arrayawaitcontext.resourceManager.getRawFileContent(SEED_PATH);constdecoder:util.TextDecoderutil.TextDecoder.create(utf-8);constseed:SeedDeckJsonJSON.parse(decoder.decodeToString(raw))asSeedDeckJson;constnow:numberDate.now();constanswers:Answer[](seed.answers??[]).map((t:string):stringt.trim()).filter((t:string):booleant.length0).map((t:string):Answer({id:newId(a),text:t,createdAt:now}));这段代码背后的工程意图是把职责停在正确层级页面只处理交互和展示服务层处理业务规则仓库层处理持久化细节配置文件说明运行时归属。只要这个边界不乱后续增加页面、换资源或做发布自查时就不会牵一发动全身。5. 写入后必须通知页面Preferences 负责持久化AppStorage负责运行期联动。播种成功后只写 Preferences首页的StorageLink(currentDeckId)不会自动知道当前题库只写currentDeckId题库列表页也不一定刷新。因此项目同时写当前题库和更新时间戳。awaitPreferencesStore.setString(PrefStoreName.App,AppPrefKey.CurrentDeckId,deck.id);AppStorage.setOrCreate(AppStorageKey.CurrentDeckId,deck.id);AppStorage.setOrCreate(AppStorageKey.LastDeckUpdateAt,deck.updatedAt);这段代码背后的工程意图是把职责停在正确层级页面只处理交互和展示服务层处理业务规则仓库层处理持久化细节配置文件说明运行时归属。只要这个边界不乱后续增加页面、换资源或做发布自查时就不会牵一发动全身。6. 首页只相信运行期状态首页没有自己去读 Preferences它只订阅当前题库 id。这样页面职责非常轻只管用户输入、跳转和展示持久化是否可用、默认题库是否存在都由启动链路兜住。这个边界让首页代码更容易排查。StorageLink(currentDeckId)currentDeckId:stringDEFAULT_DECK_ID;privateonSubmit():void{if(!this.currentDeckId){promptAction.showToast({message:题库尚未就绪请稍候});return;}this.pathStack.pushPath({name:RouteName.Drawing,param:{deckId:this.currentDeckId,question:this.question.trim()||undefined}});}这段代码背后的工程意图是把职责停在正确层级页面只处理交互和展示服务层处理业务规则仓库层处理持久化细节配置文件说明运行时归属。只要这个边界不乱后续增加页面、换资源或做发布自查时就不会牵一发动全身。7. 日志要能说明启动分支启动问题最怕只看到“加载失败”。建议把跳过、首启、缺失、自愈、版本升级分支都打出来。这个项目已经在SeedLoader中区分了 skip、seeded、rewrote 三类日志真机排查时可以直接看是哪条分支在工作。SeedLoader: skip: decks3 defaultAnswers226 seedV2 SeedLoader: seeded default deck count226 v2 SeedLoader: rewrote default deck (version-upgrade) count226 oldV1 newV2这段代码背后的工程意图是把职责停在正确层级页面只处理交互和展示服务层处理业务规则仓库层处理持久化细节配置文件说明运行时归属。只要这个边界不乱后续增加页面、换资源或做发布自查时就不会牵一发动全身。8. 验证路径启动链路的验证不是只看首页是否出现而是按数据、状态、页面三个层级走一遍。先清应用数据启动确认默认题库写入再保留用户自建题库升级种子版本确认只改默认题库最后进入题库列表和抽取页确认StorageLink驱动的页面都刷新。Write-HostDebug 构建D:\Program Files\Huawei\DevEco Studio\tools\hvigor\bin\hvigorw.batassembleHap--no-daemonWrite-Host观察启动日志时重点过滤hdc shell hilog|Select-StringSeedLoader|PreferencesStore|EntryAbility这段代码背后的工程意图是把职责停在正确层级页面只处理交互和展示服务层处理业务规则仓库层处理持久化细节配置文件说明运行时归属。只要这个边界不乱后续增加页面、换资源或做发布自查时就不会牵一发动全身。9. 常见问题与处理现象先看哪里处理首页提示题库尚未就绪PreferencesStore.init是否 await把loadContent放到初始化之后默认题库被重复覆盖LastSeededVersion是否写入只在版本变化、缺失或损坏时重写题库列表不刷新LastDeckUpdateAt是否变化写入后同步更新 AppStorage老用户题库丢失是否覆盖了所有 index播种只处理默认题库不清空用户题库privateasyncensureCurrentDeck():Promisevoid{constcur:stringawaitPreferencesStore.getString(PrefStoreName.App,AppPrefKey.CurrentDeckId,);constfallback:stringcur||(awaitDeckRepository.loadIndex())[0]||DEFAULT_DECK_ID;awaitPreferencesStore.setString(PrefStoreName.App,AppPrefKey.CurrentDeckId,fallback);AppStorage.setOrCreate(AppStorageKey.CurrentDeckId,fallback);AppStorage.setOrCreate(AppStorageKey.LastDeckUpdateAt,Date.now());}这段代码背后的工程意图是把职责停在正确层级页面只处理交互和展示服务层处理业务规则仓库层处理持久化细节配置文件说明运行时归属。只要这个边界不乱后续增加页面、换资源或做发布自查时就不会牵一发动全身。验证清单清应用数据后从冷启动走一遍确认默认数据、页面状态和日志分支符合预期。对每个写路径准备正常、空值、重复、越界四类输入确认错误停在 service 层。页面返回、重新进入、切换题库、收藏或删除后确认列表通过更新时间戳重新加载。修改资源或模块归属后重新构建确认 HAP、HAR、HSP 的依赖方向没有反转。涉及发布的内容必须用真机或构建产物确认不能只凭源码判断。小结The_Book_of_Answers的序列 1 不是在讲一个孤立 API而是在讲一个小型离线 HarmonyOS 应用怎样把具体问题做成可维护链路。把入口、状态、持久化、页面和发布清单分开后项目的复杂度不会消失但每个问题都有了明确入口。AR、HSP 的依赖方向没有反转。涉及发布的内容必须用真机或构建产物确认不能只凭源码判断。小结The_Book_of_Answers的序列 1 不是在讲一个孤立 API而是在讲一个小型离线 HarmonyOS 应用怎样把具体问题做成可维护链路。把入口、状态、持久化、页面和发布清单分开后项目的复杂度不会消失但每个问题都有了明确入口。
延伸阅读

更多相关文章

2026/9/10 4:58:34

宝马发动机烧机油问题分级维修方案详解

1. 项目概述:宝马烧机油问题的行业现状 在高端汽车维修领域,宝马发动机烧机油问题一直是困扰车主和维修技师的"老大难"。特别是2010-2018年间生产的N20、N55等系列发动机,普遍存在每1000公里消耗0.5-1升机油的情况。这个问题在温州…

2026/9/10 15:38:18

宝马N20/B48发动机烧机油分级诊断与维修方案

1. 项目背景与核心价值在浙南地区汽车后市场服务领域,宝马车型烧机油问题一直是困扰车主的典型故障。作为瑞安地区拥有15年德系车维修经验的资深技师,我们团队针对2014-2019款N20/B48发动机车型的系统性解决方案,经过三年本地化验证已形成标准…

2026/9/13 9:02:28

商用密码应用安全性评估(密评)要点与题库解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/13 9:02:28

VMware虚拟机网络连接问题排查与解决指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/13 8:57:28

Linux垃圾文件清理与磁盘空间释放实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/13 0:01:16

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/13 0:01:16

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/12 6:29:36

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/12 14:32:17

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/12 6:37:43

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码