HarmonyOS实战:源码架构与首页联动解析
留痕 HarmonyOS 实战系列

图1:第二章封面,先从首页串起拍照、录音、记录和统计。
先把源码地图铺开
第一篇已经把工程跑起来了,这一篇开始进入源码导览。对于留痕这个项目来说,真正值得先看的不是某一个组件,而是首页、服务层、路由和数据模型如何连成一条线。只要把这条线看清楚,后面拍照、录音、水印、记录、统计五条功能支路就都好理解了。
本章的阅读目标很明确:先知道哪些文件负责什么,再看首页怎么刷新,最后确认页面切换和数据重算是怎样把整个应用串起来的。
源码地图
|
文件/模块 |
职责 |
这一章会怎么用 |
|
entry/src/main/ets/pages/Index.ets |
首页工作台和 Tab 切换 |
看它怎么刷新概览、功能入口和当前页 |
|
dynamiclibrary/src/main/ets/services/WorkClockService.ets |
业务服务、状态持久化和统计汇总 |
看它怎么提供首页数据和记录数据 |
|
staticlibrary/src/main/ets/models/WorkClockModels.ets |
WorkRecord、WatermarkTemplate 等共享模型 |
看数据字段是如何统一的 |
|
entry/src/main/ets/common/Routes.ets |
页面路由和功能入口映射 |
看首页按钮如何跳到各功能页 |
|
staticlibrary/src/main/ets/common/AppTabs.ets |
底部 Tab 常量 |
看首页、记录、相机、统计、我的如何切换 |
|
entry/src/main/ets/entryability/EntryAbility.ets |
入口初始化和主页加载 |
看应用启动后首页怎么出现 |

图2:应用源码架构图,UI、Service、Model 和 Route 四层关系一眼就能看清。
首页为什么能自己刷新
首页的关键不是静态页面,而是它和服务层之间的联动。Index.ets 会在页面显示时读取本地的 Tab 状态和服务层数据,只要记录或模板有变化,页面就会随着版本号刷新。这样首页、记录页和统计页看到的就是同一份数据。
|
@StorageLink(AppStorageKeys.WORKCLOCK_VERSION) |
|
刷新来源 |
读取内容 |
页面结果 |
|
AppStorageKeys.MAIN_TAB |
上次选中的 Tab |
页面恢复到用户离开时的模块 |
|
WORKCLOCK_VERSION |
服务层版本号 |
数据变化后首页重新读取 |
|
getOverviewSnapshot() |
今日记录、本月记录、项目数等概览 |
首页头部摘要同步更新 |
|
getRecords()/getRecentRecords() |
全部记录和最近记录 |
记录卡片和最近记录区刷新 |
底部 Tab 是怎么切页的
底部 Tab 看起来只是五个按钮,实际上它把整个应用的主导航固定了下来。AppTabs 给出五个稳定的 tab id,Index 再根据当前 tab 决定要渲染哪个页面。
|
export class AppTabs { |
|
@Builder |

图3:路由地图,首页和各功能页之间的关系更容易理解。
路由表把页面串起来
Routes.ets 负责把首页按钮和设置入口映射到具体页面。这样做的好处是:页面名集中管理,后续改路由时不用在每个按钮里到处找字符串。
|
export class Routes { |
|
入口 id |
对应页面 |
典型场景 |
|
capture |
FeatureCapturePage |
现场拍照 |
|
voice |
FeatureVoicePage |
现场录音 |
|
watermark |
FeatureWatermarkPage |
水印模板 |
|
project |
FeatureProjectPage |
项目管理 |
|
record |
FeatureRecordPage |
记录管理 |
|
calendar |
FeatureCalendarPage |
日历回看 |
|
stats |
FeatureStatsPage |
统计分析 |
|
settings |
FeatureSettingsPage |
设置中心 |
服务层负责什么
WorkClockService 是整个应用的数据中枢。它负责启动时初始化本地仓库、读取持久化状态、构造首页概览、提供最近记录、管理水印模板、保存分类和备注选项,以及在记录变化时重新发布版本号。
|
bootstrap(context: common.UIAbilityContext): void { |
|
getOverviewSnapshot(): OverviewSnapshot { |
|
private persist(): void { |
数据模型长什么样
UI 层和 Service 层之所以能互相理解,是因为它们共用一套模型定义。WorkRecord 承载照片和录音记录,WatermarkTemplate 承载水印样式,StatsSummary 承载统计图表的数据。
|
模型 |
核心字段 |
用途 |
|
WorkRecord |
projectName / captureDate / location / note / mediaUri / audioUri |
承载照片、录音和备注 |
|
WatermarkTemplate |
title / accentColor / backgroundColor / timeLabel / locationLabel |
承载水印模板 |
|
OverviewSnapshot |
todayPunchCount / monthlyRecordCount / projectCount / totalMinutes |
承载首页概览 |
|
StatsSummary |
metrics / trend / categories |
承载统计页图表 |
|
CalendarDay |
day / isSelected / hasRecord |
承载日历回看 |
|
export interface WorkRecord { |
读源码时建议的顺序
- 先看 `Routes.ets` 和 `AppTabs.ets`,把页面与入口的名字对上。
- 再看 `Index.ets` 的 `refreshAllData()` 和 `buildCurrentTab()`,理解首页怎样决定自己展示什么。
- 接着看 `WorkClockService` 的 `bootstrap()`、`getOverviewSnapshot()` 和 `persist()`,理解数据如何读写。
- 最后回到 `WorkClockModels.ets`,确认页面和服务共用的字段到底有哪些。
四、首页、路由和服务层的连接点
第二篇把源码地图铺开之后,最值得再单独讲清楚的,是首页、路由和服务层之间的连接点。首页不是孤立页面,`Index.ets` 负责把服务层快照重新渲染出来,`AppTabs` 负责保存底部栏状态,`Routes` 负责把功能入口映射到具体页面,`WorkClockService` 则负责提供真实数据。四者配在一起,整个项目才会看起来像一条完整链路。
|
模块 |
职责 |
这一章要盯什么 |
|
|
Index.ets |
首页状态和刷新 |
看 `refreshAllData()` 如何同步概览、记录和设置 |
|
|
Routes.ets |
功能入口映射 |
看 `featureId` 如何落到实际页面名 |
|
|
AppTabs.ets |
底部 Tab 常量 |
看首页如何记住用户上次停留的位置 |
|
|
WorkClockService.ets |
业务数据中枢 |
看概览、记录和统计如何共用同一份数据 |
|
|
private refreshAllData(resetCalendarDate: boolean): void { |
|||
如果只看某一个页面,很容易觉得留痕只是“拍照、录音、记录、统计”四五个功能块;但从源码角度看,它更像是一个共享数据中枢。首页把入口和概览聚合起来,路由把动作分发出去,服务层把数据收回来,这样后面每个功能页的回流路径才会稳定。
本篇小结
第二篇真正做的事情,是把留痕这个项目的源码地图画出来。只要你知道首页怎么刷新、路由怎么跳转、服务层怎么持久化、模型怎么承载数据,后面再看拍照、录音、水印、记录和统计,就不会再觉得每个页面都是孤立的。
下一篇会继续看入口 Ability 和首页工作台,看看应用是怎样从启动阶段进入首页,并把拍照、录音和记录入口摆到用户面前的。
今日作业
- 打开 `Index.ets`,找到 `refreshAllData()` 和 `buildCurrentTab()`,在脑子里画出首页刷新链路。
- 打开 `WorkClockService.ets`,找出 `bootstrap()`、`getOverviewSnapshot()` 和 `persist()` 的关系。
- 打开 `Routes.ets`,把 `featureId` 和实际页面名对应起来。
本章导读
这一章开始从源码地图看项目。我们不只看“能用”,还要看首页、拍照、录音、记录和统计之间是怎么互相接上的。
- 先看首页承接几个入口。
- 再看拍照和录音如何回到记录页。
- 最后看统计页如何从同一批数据重算结果。
把入口和回流关系看清楚,后面再看每一页的细节就不会散。
- 打开 `WorkClockModels.ets`,确认 `WorkRecord` 的字段能否覆盖照片和录音两条记录。
更多推荐


所有评论(0)