留痕 HarmonyOS 实战系列

图1:先看项目效果,工程跑起来之后首页工作台会承接后续所有功能。

这一篇先把工程跑起来

留痕是一个围绕现场拍照、录音、水印、记录和统计展开的 HarmonyOS 项目。第一篇不急着拆功能,而是先把工程导入、SDK、产品配置、权限和签名这些基础环节跑通。这样做的好处很直接:后面的页面、服务和数据流都建立在同一套可运行基线上,排障也会更快。

这一篇的阅读方式很简单:先看运行效果,再看工程结构,再看关键配置,最后确认入口 Ability 能把首页加载出来。等这些基础都确认无误,后面几篇再去拆首页、拍照、录音和统计,逻辑会顺很多。

十章路线图

篇章

主题

读完能得到什么

01

项目跑起来

完成工程导入、SDK、权限和真机运行检查

02

源码导览

看懂 entry、staticlibrary、dynamiclibrary 的边界

03

Stage 入口

理解 EntryAbility 和首页加载流程

04

首页工作台

掌握 Tab、摘要和最近记录刷新

05

拍照水印

把时间、定位和备注写进照片

06

录音留证

完成录音、播放和归档

07

水印样式

管理模板、颜色和字段显示

08

记录管理

实现筛选、详情和媒体回看

09

统计复盘

让图表从真实数据里长出来

10

真机交付

整理构建、签名、缓存和验收清单

源码来自哪里

工程采用多模块组织方式,页面入口在 entry,公共模型和常量在 staticlibrary,业务服务和本地数据在 dynamiclibrary。对实战文章来说,这样的分层最适合“先把工程跑起来,再逐个拆功能”的写法。

{
  "name": "entry",
  "dependencies": {
    "dynamiclibrary": "file:../dynamiclibrary",
    "staticlibrary": "file:../staticlibrary"
  }
}

模块

主要内容

后续章节会怎么用

entry

页面、Ability、资源、权限

首页、拍照页、录音页和记录页都从这里进入

staticlibrary

WorkRecord、WatermarkTemplate、AppStorageKeys

后面章节直接复用统一数据结构

dynamiclibrary

WorkClockService、状态持久化、统计汇总

首页、记录和统计都从这里取数据

SDK 与产品配置

build-profile.json5 是第一天必须看的文件。目标 SDK、兼容 SDK、运行系统、构建模式和模块列表都在这里。只要这层配置对不上,后面的代码再完整也可能停在构建或安装阶段。

{
  "products": [
    {
      "name": "default",
      "signingConfig": "default",
      "targetSdkVersion": "6.1.0(23)",
      "compatibleSdkVersion": "6.0.2(22)",
      "runtimeOS": "HarmonyOS"
    }
  ]
}

配置项

当前值

为什么要先看它

targetSdkVersion

6.1.0(23)

决定工程按哪个目标版本编译

compatibleSdkVersion

6.0.2(22)

决定可兼容的设备范围

runtimeOS

HarmonyOS

确认不是其它运行时

modules

entry / staticlibrary / dynamiclibrary

确认完整 app 包要一起构建

图2:工程导入和配置检查的路径,先把 SDK、产品配置和权限看清楚。

module.json5 和权限

主模块配置里,能看到入口 Ability、页面列表、设备类型和权限声明。留痕需要相机、麦克风和定位能力,因此权限声明集中在这三类能力上,没有多申请无关的图库读取权限。

{
  "module": {
    "name": "entry",
    "type": "entry",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone"
    ],
    "pages": "$profile:main_pages"
  }
}

"requestPermissions": [
  { "name": "ohos.permission.CAMERA" },
  { "name": "ohos.permission.MICROPHONE" },
  { "name": "ohos.permission.APPROXIMATELY_LOCATION" },
  { "name": "ohos.permission.LOCATION" }
]

权限

用途

对应场景

CAMERA

拍摄现场照片

拍照留证

MICROPHONE

录制现场语音

录音留证

APPROXIMATELY_LOCATION

粗略定位兜底

水印地址

LOCATION

定位写入记录

水印地址和记录归档

AppScope 和签名脱敏

AppScope/app.json5 负责应用级身份信息,比如包名、版本、图标和展示名。公开文章里可以展示字段结构,但证书路径、密码、profile 路径和设备标识必须统一脱敏。

{
  "app": {
    "bundleName": "com.example.project027",
    "versionCode": 1000000,
    "versionName": "1.0.0",
    "buildVersion": "1",
    "icon": "$media:layered_image",
    "label": "$string:app_name"
  }
}

{
  "name": "default",
  "type": "HarmonyOS",
  "material": {
    "storeFile": "<local-cert-path>",
    "storePassword": "******",
    "keyAlias": "<alias>",
    "keyPassword": "******",
    "profile": "<profile-path>",
    "certpath": "<cert-path>"
  }
}

需要脱敏的内容

推荐写法

原因

证书路径

<local-cert-path>

避免暴露本机目录

密码字段

******

避免泄露签名材料

profile/certpath

<profile-path>/<cert-path>

避免暴露文件位置

设备信息

<device-id>

避免把真机标识写进正文

入口 Ability 怎么把首页带出来

项目跑起来之后,最关键的是确认入口 Ability 能把主页面加载出来。EntryAbility 负责初始化服务和打开首页,后面的首页工作台、拍照入口、录音入口和记录管理才有机会出现在用户眼前。

export default class EntryAbility extends UIAbility {
  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;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }
}

跑出来应该是什么效果

真正跑通后,应用会先进入留痕首页。首页承接今日概览、快捷入口、最近记录和统计入口,拍照、录音、水印、记录和统计这些功能都从这里继续展开。第一篇不要求把所有业务都讲完,但至少要确认首页能正常打开、按钮能点、页面能切换。

图3:真机运行检查清单,确认工程跑通后再继续往下做功能。

再看一层:首页工作台与功能入口回流

工程真正跑起来之后,最先确认的不是某一个业务细节,而是首页工作台能不能接住整条主线。首页在留痕项目里承担的是“入口汇聚”的角色:拍照、录音、水印、记录和统计都从这里出发,再回到首页完成查看、回流和继续处理。

图4:首页工作台与功能入口回流图,先把入口、回流和最近记录这条线看清楚。

这张图更像是第一篇的收尾检查:只要首页能稳稳接住入口,页面切换顺畅,后面的拍照、录音和统计就不是散开的功能点,而是一条能继续往下写的产品链路。

检查点

通过标准

先看哪里

工程导入

DevEco 能识别完整项目结构

项目目录和 Sync Project

依赖同步

entry 能引用两个本地库模块

entry/oh-package.json5

构建

assembleApp 能输出完整安装包

hvigor 构建日志

安装

真机签名校验通过

bundleName、profile、UDID

启动

EntryAbility 能加载首页

Routes.MAIN_PAGE 和 HiLog

权限

相机、麦克风、定位按需申请

module.json5 和权限弹窗

发布前再过一遍

如果文章要公开发布,截图和代码块里最容易漏掉的是本机路径、证书信息和构建缓存。下面这几项建议在写文章之前先统一处理掉。

内容

处理方式

目的

证书路径

替换为占位符

避免暴露本机目录

密码字段

替换为 ******

避免泄露签名材料

UDID/设备名

不要进入正文

避免暴露设备信息

build/oh_modules

不作为正文重点

避免读者误把生成目录当源码

  • 构建产物只用于本地验证,公开文章里不需要把生成目录展开说明。
  • 页面代码可以展示关键片段,但不要把敏感路径和密钥原文贴出来。
  • 如果截图里有证书路径或设备信息,先裁掉或改成占位符。

三、把构建、权限和签名再对一遍

第一篇真正要补强的,不是再解释一次“怎么导入工程”,而是把构建、权限和签名这三条线连起来。只要 build-profile.json5、module.json5 和 AppScope/app.json5 对齐,真机安装和首页启动就不会在最前面卡住。对于一个要持续写十章的项目来说,第一篇必须先把地基压实,后面的拍照、录音、水印和统计才有稳定的起点。

检查项

对应文件

读者收益

目标/兼容 SDK

build-profile.json5

先确认编译和安装基线一致

入口与权限

module.json5

确认首页启动和相机/麦克风/定位声明可用

应用标识与资源

AppScope/app.json5

避免包名、图标和名称不一致

真机安装链路

签名配置与设备授权

减少“能编译不能安装”的误判

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

这一段最值得带走的经验很简单:不要把问题归到页面代码上,先看工程配置、证书和设备状态。只要把这几项对齐,工程跑起来的概率会大很多,排查路径也会短很多。

四、把数据闭环写成能落地的链路

第一篇真正值钱的,不只是把工程导入成功,而是把 EntryAbility、WorkClockService、首页刷新和统计重算串成一条能落地的链路。留痕的页面不应该自己造数据,页面负责发起操作和展示结果,真正的数据写入、读取和重算都应落到服务层。

当一条记录被保存后,首页、记录页和统计页都会从同一份服务层数据重新渲染。这样写的好处是明确:读者从第一篇就能看懂,后面拍照、录音、水印和统计不是散开的功能点,而是同一套业务数据在不同页面的不同视图。

图5:留痕数据闭环,首页、记录和统计都从同一份服务层数据出发

环节

对应代码

这一环要看什么

启动能力

EntryAbility.onCreate() -> WorkClockService.bootstrap(context)

先把服务和 AppStorage 基线初始化好

首页刷新

Index.refreshAllData()

读取 overview、actions、records、recentRecords、settings

记录写入

WorkClockService.saveRecord()

normalizeRecord 之后再 persist

统计重算

WorkClockService.getStatsSummary(range)

从同一份 records 重建 metrics、trend 和 categories

版本刷新

AppStorageKeys.WORKCLOCK_VERSION

记录变化后触发页面重刷

export default class EntryAbility extends UIAbility {
  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 {
  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();
}

saveRecord(record: WorkRecord): void {
  const payload: WorkRecord = this.normalizeRecord(record);
  const index: number = this.records.findIndex((item: WorkRecord) => item.id === payload.id);
  if (index >= 0) {
    this.records[index] = payload;
  } else {
    this.records.unshift(payload);
  }
  this.persist();
}

把这条链路看清楚之后,再看第一篇的意义就会很明确:它不是在讲一个“导入工程”的步骤,而是在证明整个项目已经有了统一的数据入口、统一的服务层和统一的页面刷新节奏。

本篇小结

第一篇的目标是把工程跑起来,而不是把所有功能都讲完。只要你已经确认 SDK 对齐、模块依赖同步、权限声明最小化、签名配置可用、入口 Ability 能打开首页,那么这个项目就已经具备继续展开拍照、录音、水印和统计功能的基础。

下一篇会从源码导览开始,继续拆 entry、staticlibrary 和 dynamiclibrary 三个模块,让整套留痕应用的结构更清楚。

今日作业

  • 在 DevEco Studio 中导入工程,执行 Sync Project。
  • 检查 build-profile.json5、module.json5 和 AppScope/app.json5 的核心字段。
  • 构建并安装完整 app 包,确认首页能够正常打开。
Logo

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

更多推荐