当前位置: 首页 > news >正文

第04篇|Stage模型启动链路:EntryAbility到首页加载解析

这一章只看一件事:应用为什么能先把服务层准备好,再把首页稳稳地加载出来。

图 4-1 章首页封面:Stage 模型入口与首页加载链路

维度

内容

本章主题

EntryAbility 如何在 Stage 模型下把“留痕”带到首页

核心源码

EntryAbility.ets、main_pages.json、module.json5、WorkClockService.ets、Index.ets

阅读目标

看懂启动链路、配置职责和首页数据为什么能在启动后直接出现

先给结论在 Stage 模型里,真正的入口不是首页页面本身,而是 EntryAbility。EntryAbility 负责先 bootstrap,再 loadContent,首页只是最后被加载出来的那一页。

本章导读

这一章只看入口链路:EntryAbility 怎么把首页送到用户面前,main_pages.json 和 module.json5 又各自负责什么。

  • 看启动顺序怎么走。
  • 看首页为什么能一打开就有数据。
  • 看哪些配置决定首屏和路由。

入口链路理顺之后,再看首页的数据渲染就会自然很多。

一、Stage 模型下,真正的入口是 EntryAbility

在这个项目里,应用从桌面图标被点开之后,最先被系统调用的是 EntryAbility。它不是一个普通页面,而是负责把应用从“启动态”切换到“可见态”的第一道闸门。这个阶段如果没有把服务层和窗口链路准备好,首页就算能渲染出来,也只会是一张空壳。

import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { WorkClockService } from 'dynamiclibrary';
import { Routes } from '../common/Routes';

export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
WorkClockService.getInstance().bootstrap(this.context);
} catch (err) {
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
}
}

onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent(Routes.MAIN_PAGE, (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}
}

阶段

关键动作

效果

onCreate

先切换颜色模式,再调用 WorkClockService.bootstrap()

服务层在首页出现之前完成准备

onWindowStageCreate

通过 Routes.MAIN_PAGE 加载首页

入口和页面路由保持统一

loadContent 失败

记录 hilog 日志

便于定位白屏或路由错误

图 4-2 启动链路:系统启动 -> EntryAbility -> bootstrap -> 首页

二、先 bootstrap,再 loadContent

EntryAbility.onCreate 里最关键的动作不是页面渲染,而是 WorkClockService.bootstrap。它把本地持久化数据、默认模板和 AppStorage 状态先准备好,再把首页交给 windowStage.loadContent。这样做的好处很直接:首页第一次进入时拿到的就是可用数据,而不是临时拼出来的默认壳。

bootstrap(context: common.UIAbilityContext): void {
AppStorage.setOrCreate<number>(AppStorageKeys.WORKCLOCK_VERSION, 0);
AppStorage.setOrCreate<string>(AppStorageKeys.MAIN_TAB, AppTabs.HOME);
WorkClockRepository.initialize(context);

const raw: string = WorkClockRepository.readState();
if (raw.length > 0) {
const state: WorkClockState = this.parseState(raw);
this.records = state.records;
this.watermarkTemplates = state.watermarkTemplates ?? WorkClockService.buildDefaultWatermarkTemplates();
this.selectedWatermarkTemplateId = state.selectedWatermarkTemplateId ?? this.watermarkTemplates[0].id;
this.categoryOptions = state.categoryOptions ?? WorkClockService.buildDefaultCategoryOptions();
this.noteOptions = state.noteOptions ?? WorkClockService.buildDefaultNoteOptions();
this.publishSelectedWatermarkSnapshot();
this.bumpVersion();
return;
}

this.publishSelectedWatermarkSnapshot();
this.persist();
}

步骤

做什么

结果

初始化 AppStorage

写入 WORKCLOCK_VERSION 和 MAIN_TAB

首页和服务层有统一的轻量状态入口

读取持久化状态

从 WorkClockRepository 取回 JSON

已有记录和模板可以直接恢复

回填默认值

当本地没有数据时装入默认模板和分类

首次安装也不会空白

发布水印快照

把当前模板写回 AppStorage

水印页和拍照页能同步读取

这一段的关键bootstrap 不是“初始化一下就完了”,而是把首页需要的默认状态、模板快照和持久化入口一次性搭好。首页之所以能直接展示内容,是因为这一步已经先把底子铺好了。

三、main_pages.json 和 module.json5 各管一半

页面清单和 Ability 配置是两层职责。main_pages.json 只告诉系统哪些页面可以被加载,module.json5 则负责声明入口 Ability、权限和启动能力。两份文件各管一半,缺一块都不行。

{
"src": [
"pages/Index",
"pages/FeatureCapturePage",
"pages/FeatureVoicePage",
"pages/FeatureWatermarkPage",
"pages/FeatureProjectPage",
"pages/FeatureRecordPage",
"pages/FeatureCalendarPage",
"pages/FeatureStatsPage",
"pages/FeatureSettingsPage"
]
}

{
"module": {
"name": "entry",
"type": "entry",
"mainElement": "EntryAbility",
"pages": "$profile:main_pages",
"requestPermissions": [
{ "name": "ohos.permission.CAMERA", "reason": "$string:camera_permission_reason" },
{ "name": "ohos.permission.MICROPHONE", "reason": "$string:microphone_permission_reason" },
{ "name": "ohos.permission.APPROXIMATELY_LOCATION", "reason": "$string:location_permission_reason" },
{ "name": "ohos.permission.LOCATION", "reason": "$string:location_permission_reason" }
]
}
}

图 4-3 配置关系:main_pages.json、module.json5 与 EntryAbility 的分工

配置项

作用

本章关注点

mainElement

指向入口 Ability

系统先找到 EntryAbility,再决定加载什么页面

pages

绑定页面清单

首页和功能页都从这里被注册

requestPermissions

声明运行权限

相机、麦克风和定位都在这里提前声明

skills / actions

支持桌面入口

保证桌面点击能够进入应用

四、首页为什么一打开就有数据

首页不是自己去拉一份孤立的数据,而是通过 AppStorage 里的轻量状态和服务层的快照来渲染。WorkClockService 负责维护真实数据,Index 负责在页面生命周期里把这些数据读出来,再渲染成首页、记录和统计卡片。

export class AppStorageKeys {
static readonly MAIN_TAB: string = 'main.tab';
static readonly WORKCLOCK_VERSION: string = 'workclock.version';
static readonly WATERMARK_TEMPLATE_ID: string = 'watermark.templateId';
static readonly WATERMARK_CATEGORY: string = 'watermark.category';
static readonly WATERMARK_TITLE: string = 'watermark.title';
static readonly WATERMARK_NOTE: string = 'watermark.note';
static readonly WATERMARK_ACCENT_COLOR: string = 'watermark.accentColor';
static readonly WATERMARK_BACKGROUND_COLOR: string = 'watermark.backgroundColor';
}

@StorageLink(AppStorageKeys.WORKCLOCK_VERSION)
@Watch('handleWorkClockVersionChanged')
private workclockVersion: number = 0;

aboutToAppear(): void {
this.refreshAllData(true);
}

onPageShow(): void {
this.refreshAllData(false);
}

private refreshAllData(resetCalendarDate: boolean): void {
const storedTab: string | undefined = AppStorage.get(AppStorageKeys.MAIN_TAB) as string | undefined;
if (storedTab && storedTab.length > 0) {
this.currentTab = storedTab;
}

this.overview = this.service.getOverviewSnapshot();
this.actions = this.service.getHomeActions();
this.records = this.service.getRecords();
this.recentRecords = this.service.getRecentRecords();
this.featureBullets = this.service.getFeatureBullets();
this.techFeatures = this.service.getTechFeatures();
this.usageScenes = this.service.getUsageScenes();
this.settings = this.service.getSettings();
this.syncCalendarDisplayDate(resetCalendarDate);
}

private bumpVersion(): void {
const current: number | undefined = AppStorage.get(AppStorageKeys.WORKCLOCK_VERSION) as number | undefined;
AppStorage.setOrCreate<number>(AppStorageKeys.WORKCLOCK_VERSION, (current ?? 0) + 1);
}

Key

写入方

读取方

MAIN_TAB

WorkClockService.bootstrap / Index.switchTab

Index.refreshAllData

WORKCLOCK_VERSION

WorkClockService.bumpVersion

Index.@StorageLink + @Watch

WATERMARK_TEMPLATE_ID

publishSelectedWatermarkSnapshot

水印页和拍照页

WATERMARK_TITLE / NOTE / COLOR

publishSelectedWatermarkSnapshot

水印编辑页预览和保存

一句话记住它AppStorage 不是数据库,它只负责让页面之间共享轻量状态;真正的业务数据仍然由服务层和本地存储负责。

再看一层:启动链路里最该盯住的三处

第四篇把入口链路串起来之后,真正值得在文章里再多说一遍的,是启动阶段最该盯住的三处:`EntryAbility` 的初始化、`main_pages.json` 的页面清单、`module.json5` 的入口与权限声明。只要这三处同时对齐,首页才有机会在启动后稳稳落地。

阶段

关键动作

读者看到的结果

onCreate

先 bootstrap 服务层

本地数据和默认状态提前准备好

onWindowStageCreate

再 loadContent 主页面

首页被稳定装载到窗口中

main_pages.json

声明可加载页面

首页和功能页路由来源清晰

module.json5

声明入口和权限

相机、麦克风和定位链路更完整

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.context.getApplicationContext().setColorMode(
ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET
);
WorkClockService.getInstance().bootstrap(this.context);
}

onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent(Routes.MAIN_PAGE, (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
});
}

很多真机问题看上去像页面问题,实际上是入口阶段没有把服务和窗口准备好。把启动链路写清楚,文章本身会更完整,读者在跑项目时也更容易知道“先查哪里、后查哪里”。

五、把启动链路和首页数据闭环合起来

这一段把第 4 章再往前推一步:EntryAbility 不只是把首页送出来,还要先把服务层和 AppStorage 的轻量状态准备好。这样首页打开时拿到的不是临时壳,而是一份已经可以直接渲染的快照。

从这个角度看,main_pages.json 和 module.json5 也不只是静态配置,而是启动链路能否稳稳落地的关键边界。只要入口、页面清单和权限声明对齐,首页就能沿着同一条数据链路继续工作。

图4-4 启动后的数据闭环:EntryAbility 先准备服务层,再把首页快照交给 Index。

启动阶段

代码

目的

EntryAbility.onCreate()

WorkClockService.bootstrap(context)

先把持久化和默认状态准备好

windowStage.loadContent()

Routes.MAIN_PAGE

把首页送到用户面前

Index.aboutToAppear()/onPageShow()

refreshAllData()

首页一打开就读到最新快照

WORKCLOCK_VERSION

AppStorage

记录变化后触发页面重绘

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.context.getApplicationContext().setColorMode(
ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET
);
WorkClockService.getInstance().bootstrap(this.context);
}

private refreshAllData(resetCalendarDate: boolean): void {
this.overview = this.service.getOverviewSnapshot();
this.records = this.service.getRecords();
this.recentRecords = this.service.getRecentRecords();
}

private handleWorkClockVersionChanged(): void {
this.refreshAllData(false);
}

把这一层再压实一次,第四章就不只是“入口能打开”,而是“入口打开后马上就能看到可以继续往下跑的真实数据”。这也是留痕后面几章能够顺着写下去的前提。

六、本章小结

第四章把启动链路真正串了起来:EntryAbility 先做服务初始化,再加载首页;main_pages.json 和 module.json5 各自负责页面清单和入口配置;Index 通过 AppStorage 的版本号和主 Tab 状态,把服务层快照渲染成用户能看到的数据。这样一来,首页一打开就有内容,后面的拍照、录音和记录页也都能沿着同一套状态链路工作。

今日实操

步骤

检查点

完成后看到什么

查看 EntryAbility

确认 onCreate 与 onWindowStageCreate 的职责

知道应用为什么先进入入口再进入首页

查看 bootstrap

确认服务层在首页前完成初始化

本地数据和模板快照已经准备好

查看 Index

确认 @StorageLink 和 refreshAllData 的作用

首页切换和刷新都能跟着版本号走

如果你在真机上点开“留痕”,首页能够立刻看到卡片、记录和状态,说明这一章的链路已经跑通了。

http://www.rkmt.cn/news/1507003.html

相关文章:

  • Redis Stack 初探:为什么它是 AI 检索的“新基建”?
  • 深度实战:Python爬虫爬取古诗文网指定作者全部诗文——从编码陷阱到正则清洗的全流程解析
  • 深圳钣金外壳定制
  • 如何在5分钟内免费激活Unity全版本:UniHacker一站式解决方案
  • 实战复盘:我们如何用SageMaker Canvas将货物延迟预测准确率提升了30%
  • 手把手教你写一个Linux PCIe设备驱动:从`lspci`到`probe`函数的完整流程
  • 3步让你的代码编辑器颜值翻倍:Maple Mono字体完全指南
  • 告别模组管理噩梦:XCOM 2 Alternative Mod Launcher 终极解决方案
  • Windows 11 LTSC版本微软商店自动化部署指南
  • 别再花钱买服务器了!手把手教你用旧电脑搭建Proxmox VE家庭虚拟化平台
  • Convert2ModuleNameTreeNode讲解
  • Java毕设选题推荐:基于springboot和vue的高校学生二手书交易校园二手书交易系统【附源码、mysql、文档、调试+代码讲解+全bao等】
  • Trumbowyg:终极轻量级WYSIWYG编辑器解决方案
  • 终极网盘下载解决方案:免费油猴脚本一键获取六大云盘直链
  • 暗黑2存档编辑器终极指南:专业玩家的存档管理神器
  • AI 开发 App 工具有哪些?2026 年主流平台全面盘点
  • 深入解析PowerPC G4 MPC7457:经典RISC处理器的微架构与硬件设计
  • 从原理图到PCB的Altium Designer 20高效操作链:我的私藏快捷键组合
  • 船舶振动分析与数据可视化
  • FitNets:从“中间层提示”到“深度瘦身”的蒸馏实战
  • 深度强化学习中的后门攻击原理与防御
  • Adobe-GenP 3.0破解工具:一键激活Adobe Creative Cloud的终极指南
  • 告别ImageNet偏差:手把手教你用PatchCore+ResNet50搭建工业缺陷检测模型(附代码)
  • 软考系统规划与管理师到底是干嘛的?用“大厂物业经理”的逻辑带你了解软考系规
  • AI Agent的产品化思考:用户体验、价值主张与GTM策略
  • VM-UNet 在 ARCADE 数据集上的医学图像分割完整复现指南
  • MPC8347EA硬件设计深度解析:电源时序、DDR接口与调试实战
  • 3分钟掌握手机号码精准定位:location-to-phone-number完全指南
  • MPC8641硬件设计实战:阻抗匹配、配置引脚与JTAG接口的深度解析
  • 别再手动拼接字节了!用Python的modbus_tk库优雅处理32位浮点数传输