第2篇|从功能到技术栈:时间轨迹这类鸿蒙应用是怎么搭起来的

如果说第一篇更像是在讲“某一个页面怎么做”,那这一篇我想换个角度,讲清楚这类应用是怎么从功能、状态、权限、数据到页面,一层一层搭起来的。

这次的重点不放在某一个页面本身,而是放在整个应用的组织方式上。因为真正能把“拍照、水印、工作记录、相册、模板、账号、AI 工具”串起来的,不是某个炫技控件,而是一套足够清楚的技术栈和分层方式。

先看整体结构

EntryAbility.ets

Index.ets

HomePage

CameraPage

TemplatePage

ProfilePage

TimeLocationPage

WorkRecordPage

SettingsPage

PhotoAlbum / VideoAlbum / AITools / Scanner

PersistentStorage / AppStorage

AccountService

session.json / accounts/openId/userdata.json

CameraService

PermissionUtil

LocationService

watermark-camera HAR

customTemplatesJson

wmLocationHistory / wmManualLocation

wrRecords / wrCustomTypes

在这里插入图片描述

这张图想表达的不是“页面很多”,而是“每一层各干各的”:

  • EntryAbility 负责启动和全局初始化
  • Index.ets 负责路由和页面切换
  • CameraServiceLocationServicePermissionUtil 负责能力封装
  • AccountService 负责账号与本地数据隔离
  • HomePageCameraPageTemplatePageProfilePage 等负责业务表达

这套技术栈,核心不是多,而是稳

层级 主要模块 作用
启动层 EntryAbility.ets 初始化持久化数据、窗口样式、系统栏和安全区
路由层 Index.ets 管理底部 tab、子页面返回、登录跳转
状态层 AppStorage / PersistentStorage / @StorageLink 管理全局状态和用户偏好
能力层 CameraService / LocationService / PermissionUtil 封装拍照、定位、权限申请
数据层 AccountService 保存会话、账号列表、用户数据
复用层 watermark-camera HAR / has 共享相机、水印、导航栏等能力

这套结构的好处很直接:业务页面不会直接和底层能力硬耦合,后续加功能的时候,不用到处改。

入口层:先把应用“站稳”

应用真正跑起来之前,先做两件事最重要。

第一件,是把用户会反复用到的状态提前放好。

PersistentStorage.persistProp('wmAutoWatermark', true);
PersistentStorage.persistProp('wmShowTime', true);
PersistentStorage.persistProp('wmShowAddress', true);
PersistentStorage.persistProp('wrAutoRecord', true);
PersistentStorage.persistProp('wmLocationHistory', '[]');
PersistentStorage.persistProp('wrRecords', '[]');

第二件,是把窗口和系统栏先处理好,让内容区真正可以“沉浸式”展开。

win.setWindowLayoutFullScreen(true);
win.setWindowSystemBarProperties({
  statusBarColor: '#00000000',
  navigationBarColor: '#00000000'
});

这两步看起来不起眼,但它们决定了整个应用的底座。状态不乱,布局不乱,后面的页面才不会一会儿跑偏、一会儿重算。

路由层:一个入口,多条业务线

Index.ets 的角色很像总调度。它不负责做具体业务,它只负责判断当前应该显示哪一页。

if (this.selectedTab === 'home') {
  HomePage({ ... })
} else if (this.selectedTab === 'camera') {
  CameraPage({ ... })
} else if (this.selectedTab === 'templates') {
  TemplatePage({ ... })
} else if (this.selectedTab === 'profile') {
  ProfilePage({ ... })
}

这里最值得提的一点,是它把“主页面”和“子页面”分开了。

  • 主页面是 homecameratemplatesprofile
  • 子页面是 timeLocationworkRecordsettingsphotoAlbumvideoAlbumloginPage

这样做的结果就是:底部导航始终稳定,局部业务页面可以自由扩展,不会把整个壳子搞乱。

数据层:账号和本地数据分开存

这部分我觉得是这套应用里最成熟的地方之一。

AccountService 没有把所有数据硬塞进一个地方,而是做了三层区分:

  • session.json:只存当前会话
  • device_accounts.json:存设备上的账号列表
  • accounts/{openId}/userdata.json:存某个账号自己的业务数据

也就是说,登录态和业务数据是分开的,用户数据和设备数据也是分开的。

if (!this.isLoggedIn) {
  let openId = this.huaweiOpenID;
  let nickname = this.huaweiNickname;
  if (!openId) {
    openId = AccountService.getSessionOpenId(ctx);
    nickname = AccountService.getSessionNickname(ctx);
  }
  if (openId !== '') {
    AccountService.loadUserData(ctx, openId);
    this.huaweiOpenID = openId;
    this.huaweiNickname = nickname;
    this.isLoggedIn = true;
  }
}

这段恢复逻辑的价值很大:

  1. 用户重新打开应用时,不需要重新配置一遍
  2. 多账号切换时,数据不会互相覆盖
  3. 退出登录后,本地数据仍然能留在设备上,后续可以恢复

这就是“功能可用”和“产品可持续用”的区别。

能力层:把复杂系统藏进服务里

【时间轨迹】软件,可以在华为应用市场中下载,或在百度云下载:https://appgallery.huawei.com/app/detail?id=com.example.app_22&channelId=SHARE&source=appshare

真正能把应用做厚的,不是页面多,而是服务层稳。

1. 相机能力

CameraPage 并不是简单地拍一张照片,它实际上在做一条完整链路:

  • 申请相机和存储权限
  • 初始化相机预览
  • 监听设备方向和传感器
  • 管理拍照 / 录像 / 对焦 / 闪光灯 / 变焦
  • 在保存时叠加水印
  • 把结果保存到本地或系统相册
const results = await Promise.all([
  PermissionUtil.requestCameraPermissions(ctx),
  PermissionUtil.requestMediaPermissions(ctx),
]);
this.permissionGranted = results[0];
this.startLocationWatch();

2. 定位能力

定位不是独立展示,而是直接参与水印内容生成:

  • 当前地址可显示
  • 经纬度可补充
  • 手动地点可覆盖
  • 历史地点可回看

也就是说,定位不是“展示信息”,而是“业务输入”。

3. 权限能力

PermissionUtil 把授权拆成了几个清晰的方法:

  • 摄像头权限
  • 位置信息权限
  • 媒体读写权限

这样写的好处是,页面不用自己去理解权限细节,只要知道“我现在能不能做这件事”。

功能层:每个页面都在干自己的活

这个应用的页面很多,但职责并不混乱。

  • HomePage 是入口中枢,负责把功能卡片放到用户最容易看到的位置
  • CameraPage 是核心生产页,负责拍照、录像和实时水印
  • TemplatePage 负责模板浏览、收藏、最近使用、创建自定义模板
  • TimeLocationPage 负责时间、地点、历史地址和手动覆盖
  • WorkRecordPage 负责工作记录列表和分类管理
  • ProfilePage 负责账号信息、相册、扫描、收藏、最近使用、帮助和设置
  • AIToolsPage 则是能力扩展口,给后续智能功能留了位置

这里我很喜欢的一点,是首页的功能卡片不是“装饰”,而是明确的业务分发器。

new HomeFeature('1', '时间地点', '自动水印', '📍', '#EAF2FF'),
new HomeFeature('2', '工作记录', '项目记录', '📋', '#F0ECFF'),
new HomeFeature('3', '相册导入', '添加水印', '🖼️', '#E8F8EE'),
new HomeFeature('4', '水印设置', '编辑水印', '⚙️', '#FFF2E8'),

它不是单纯地“好看”,而是让用户一进来就知道自己该往哪里走。

在这里插入图片描述

为什么这套架构值得写

我觉得这套实现最值钱的地方,不是某一个炫目的功能,而是它把很多细碎能力都收进了一个统一框架里。

  • 视觉层统一:底部导航、卡片、弹窗、预览风格一致
  • 状态层统一:AppStorage / PersistentStorage 贯穿全局
  • 能力层统一:相机、定位、权限、存储都被服务层接住
  • 数据层统一:账号数据和设备数据分离,恢复逻辑清晰
  • 扩展层统一:AI 工具、证件识别、文档扫描都能继续往下长

如果只看功能,这是一款“会拍照、会记录、会管理模板”的应用;
如果看技术栈,它更像是一个把鸿蒙能力串起来的完整业务壳子。

结尾

因为真正能让一个应用长期可维护的,往往不是某个页面做得多漂亮,而是它有没有把状态、权限、账号、能力和路由分层分清楚。

时间轨迹这套实现,恰好就是一个比较完整的例子:它不是把功能堆起来,而是把功能组织起来。

1. 边界和兜底

这类文章最容易被扣分的地方,不是“有没有代码”,而是“有没有把异常情况讲清楚”。

  • 没有登录时,AccountService 不应该继续假装自己有用户数据,而是回到登录入口
  • 没有定位权限时,水印可以继续工作,但地址字段应该降级为默认文案或手动地点
  • 相机能力不稳定时,页面要给出明确提示,不要让预览区一直空白
  • 本地数据不存在时,要能回退到默认值,不能直接把页面渲染成一堆报错状态

2. 验证清单

  • 启动应用后,检查 PersistentStorage 里的默认项是否已经生效
  • 切换底部 tab,确认路由切换不会把主壳子打乱
  • 退出账号再进入,确认 session.json 和账号数据能正确恢复
  • 关闭定位权限,确认水印页面仍然能正常拍照和保存
  • 重新打开页面,确认历史记录和模板列表来自最新数据,而不是旧缓存

3. 还可以继续往下写什么

  • 深浅色适配和沉浸式布局
  • 隐私安全和沙盒保存
  • 可信存储和迁移恢复

参考

Logo

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

更多推荐