DevEco CLI实战:从命令行构建到上架鸿蒙宝贝日程表App
2026/9/15 11:59:49 网站建设 项目流程

最近在给家里小朋友做一款鸿蒙原生的「宝贝日程表」App,整个过程从命令行创建工程一路做到正式上架,就是用 DevEco CLI 这套命令行工具链完成的。这个项目本身不复杂,但正好把一个鸿蒙 App 从 0 到 1 的关键链路全跑了一遍:工程初始化、ArkTS 页面开发、本地数据持久化、hvigor 打包、签名配置、AGC 上架审核。这篇文章把整个实战过程完整复盘一遍,适合两类人看:一类是刚接触鸿蒙开发、想找个小项目练手的同学;另一类是已经在用 DevEco Studio 写界面、但还没搞明白命令行构建和上架流程的开发者。我会把实际踩过的坑也一并交代清楚,尤其是签名和构建产物那部分,官方文档里散落在各处,这里一次说透。

1. 需求拆解与整体方案设计

1.1 做「宝贝日程表」要解决什么问题

带娃的家庭应该都有画面感:每天早上催起床、催吃饭、催写作业,晚上还要盯着兴趣班时间,全靠家长人肉闹钟。我最初的想法特别简单,就是做一个让孩子自己也能看懂、能操作的小工具,把每天要做什么事、几点做,清清楚楚列出来。它的使用场景是放在平板或者智慧屏上,孩子早晨起来看一眼就知道今天安排,完成一项就自己点一下打卡。

目标用户决定了产品形态。孩子用的 App,第一个要求是字要大、按钮要大、操作路径短。第二个要求是不能有复杂的账密体系,打开就能用。第三个要求是数据要本地保存,不需要联网,也不需要服务器。所以这个项目的定位很明确:本地优先、单机可用、轻量快速。我没有做登录、云同步、分享这些功能,甚至没有做多用户体系,因为 MVP 阶段这些都属于过度设计。

核心功能收敛成三块就够用了:

  • 今日日程列表:按时间顺序展示当天的安排,已经完成的置灰或打勾。
  • 添加日程:通过一个大按钮唤起添加弹窗,填标题、选时间,点保存就上屏。
  • 本地持久化:应用杀掉再打开,日程数据不能丢。

做完这三块,这个 App 就已经能在日常使用了。至于后续的周视图、农历提醒、家长密码锁,都是可以迭代的扩展项,不影响第一版上架。

1.2 技术选型:为什么坚持用 DevEco CLI

这个项目技术栈选型其实没有太多悬念,鸿蒙原生开发现在主流就是 ArkTS + ArkUI,声明式 UI 写法,状态驱动界面刷新。但我在工程创建和构建方式上刻意选择了 DevEco CLI,而不是全程依赖 DevEco Studio 的图形界面,原因有两个。

第一,命令行工具更适合重复操作和自动化。日常开发中我经常要新建 demo 工程、跑构建、出包,如果每次都在 IDE 里点来点去,效率很低。CLI 方式下,一条命令就能完成工程创建,hvigor 一条命令就能打出 HAP,这在做多模块项目、CI/CD 流水线时尤其重要。第二,CLI 能让你更清楚构建链路的真相。IDE 图形界面把很多细节藏起来了,换成命令行之后,构建产物在哪、签名怎么配、依赖从哪拉,全都要自己面对,反而能更快理解鸿蒙项目的工程结构。

当然,我并不是说要彻底抛弃 IDE。实际开发中我还是会用 DevEco Studio 来跑模拟器、看布局效果、打断点调试,因为 ArkUI 的实时预览和调试体验确实比纯命令行舒服太多。我的建议是:工程管理和构建走 CLI,界面编写和调试配合 IDE,两者互补。

1.3 功能范围与开发里程碑

整个「宝贝日程表」从设计到上架,前后大概用了两周的业余时间。开发节奏拆成四个阶段推进,每个阶段都有明确的可验证产物:

阶段内容交付物
第一阶段工程初始化 + 页面骨架可运行的空壳 App
第二阶段日程数据模型 + 列表展示静态列表界面
第三阶段添加弹窗 + 本地持久化完整可用的单机 App
第四阶段打包签名 + 上架审核应用市场可下载的正式版本

这个拆分方式有个好处:每个阶段结束都能在真机上跑一版,及时发现问题,不用憋一个大版本到最后才验证。做个人项目最忌讳闷头写很久才拿出来跑,试错成本会很高。

2. 环境准备:CLI 工具链从零配置

2.1 安装 DevEco Studio 并找到命令行全家桶

很多人在这一步会绕弯子。DevEco CLI 并不是一个单独下载的工具,它是随着 DevEco Studio 一起安装的。安装完 DevEco Studio 之后,命令行工具就已经在你的电脑上了,只是默认没有配到 PATH 里,需要自己找到路径。

以我本机为例,命令行工具主要集中在两个位置:一个是 DevEco Studio 安装目录下的sdk目录,里面有hdc(设备调试工具)和各类 SDK 组件;另一个是工程目录下的hvigorwohpm,这两个在初始化工程之后会出现。如果你是 macOS 或 Linux 环境,建议把 DevEco Studio 的tools目录加到 shell 的 PATH 里,方便全局调用。Windows 环境同理,把对应目录加到系统环境变量即可。

配置好之后,打开终端先跑一条命令验证:

devecocli --help devecocli --version

如果终端能正常输出版本信息,说明命令行环境已经通了。这里有个小坑:新版 DevEco Studio 的命令行工具名称在不同版本里可能不完全一样,有的版本是devecocli,有的版本把能力拆得更细。遇到无法识别命令的情况,先去安装目录的bin或者tools目录下看看到底有哪些可执行文件,不要硬记命令名。

2.2 用 DevEco CLI 初始化工程模板

环境就绪之后,我用 CLI 创建了「宝贝日程表」的工程骨架。DevEco CLI 提供了初始化项目的子命令,可以指定工程名称、包名、模板类型。比如下面这条命令,就是创建一个基于 Empty Ability 模板的工程:

devecocli create -n BabySchedule -p com.example.babyschedule -t app

参数含义不复杂:-n是工程名,-p是应用包名,-t指定工程类型。创建完成后,当前目录下会生成一个完整的鸿蒙工程,结构大概是这样的:

BabySchedule/ ├── AppScope/ ├── entry/ │ ├── src/main/ │ │ ├── ets/ │ │ │ ├── entryability/ │ │ │ ├── pages/ │ │ │ └── models/ │ │ └── resources/ │ ├── build-profile.json5 │ └── hvigorfile.ts ├── build-profile.json5 ├── hvigorfile.ts └── oh-package.json5

entry就是我们最终的 HAP 模块,AppScope放应用级配置,oh-package.json5是依赖管理文件,build-profile.json5是构建配置文件。初次看到这个结构的同学不用慌,日常开发 90% 的时间只会在entry/src/main/ets下面写业务代码,其他文件偶尔动一下。

2.3 必须掌握的 hvigor 与 ohpm 命令

鸿蒙工程的构建工具叫 hvigor,对应工程根目录下的hvigorw脚本;依赖管理工具叫 ohpm,类似前端的 npm。这两个工具的常用命令,我整理了一份速查表:

场景命令
安装依赖ohpm install
单独安装某个依赖ohpm install @ohos/axios
清理构建产物./hvigorw clean
构建 HAP 包./hvigorw assembleHap
构建并执行测试./hvigorw test
查看所有 task./hvigorw tasks --type build

需要注意的是,hvigorw在执行时会自动去拉取对应版本的 hvigor 依赖,首次运行会比较慢,这是正常现象,耐心等即可。另外,assembleHap产出的包默认是带 debug 签名的,如果要在真机上安装,还需要先配置签名。这部分我在后面专门展开讲。

3. 宝贝日程表核心功能实现

3.1 数据模型与页面骨架

开发核心功能之前,先把数据模型定下来。「宝贝日程表」的数据结构非常简单,四个字段就够用:标题、日期、时间、完成状态,另外加一个唯一 id 用于列表渲染时做 key。

export interface ScheduleItem { id: string; title: string; date: string; time: string; done: boolean; }

界面我用了 ArkUI 的声明式写法。整个首页就是一个垂直布局,顶部是当天日期的大标题,中间是日程列表,底部是一个大的“添加日程”按钮。ArkUI 的ColumnListTextButton这些基础组件组合一下,页面骨架很快就搭起来了。

@Entry @Component struct Index { @State scheduleList: ScheduleItem[] = []; build() { Column({ space: 16 }) { Text(this.getToday()) .fontSize(28) .fontWeight(FontWeight.Bold) .margin({ top: 20 }) List({ space: 12 }) { ForEach(this.scheduleList, (item: ScheduleItem) => { ListItem() { ScheduleRow({ item: item, onToggle: this.toggleDone.bind(this) }) } }, (item: ScheduleItem) => item.id) } .layoutWeight(1) Button('添加日程') .width('80%') .height(48) .onClick(() => { this.showAddDialog(); }) } .width('100%') .height('100%') } }

这段代码里有几个细节值得注意:List之所以要设置layoutWeight(1),是为了让它填满按钮和标题之间的剩余空间;ForEach的第三个参数是 key 生成函数,我用item.id来保证列表项被唯一标识,这样增删改时 ArkUI 能精准复用节点,不会出现列表刷新时的闪动问题。

3.2 添加日程:弹窗交互与时间选择

添加日程的入口是底部的大按钮,点击之后弹出一个自定义弹窗。我用了 ArkUI 的CustomDialogController,在控制器里嵌入表单控件。这里有一个体验细节值得说说:如果直接上标准输入框加时间选择器,对孩子来说操作门槛还是偏高,所以我简化成两个控件——标题输入框和按小时的快速时间选择。

时间选择部分我用了TimePicker,但是把它限制在整点和半点,减少操作精度。这个交互优化对成人用户来说也能减少思考成本。核心代码大概是这样的:

@CustomDialog struct AddScheduleDialog { controller: CustomDialogController; title: string = ''; selectedTime: string = '08:00'; onConfirm: (item: ScheduleItem) => void; build() { Column({ space: 16 }) { TextInput({ placeholder: '今天要做什么?', text: this.title }) .onChange((value: string) => { this.title = value; }) TimePicker({ selected: this.selectedTime }) .useMilitaryTime(true) .onChange((value: TimePickerResult) => { this.selectedValue = `${value.hour}:${value.minute}`; }) Row({ space: 16 }) { Button('取消') .onClick(() => { this.controller.close(); }) Button('保存') .onClick(() => { const newItem: ScheduleItem = { id: Date.now().toString(), title: this.title, date: this.getToday(), time: this.selectedValue, done: false }; this.onConfirm(newItem); this.controller.close(); }) } } } }

注意这里的时间格式化,TimePicker返回的hourminute是数字类型,两位数的时间需要补零,否则会出现8:5这种不好看也不便于排序的格式。我在工具函数里做了padStart处理。

3.3 列表渲染与完成状态切换

日程列表的每一行展示两项核心信息:标题和时间,左边放一个自定义的完成状态按钮。孩子点击状态按钮之后,这一条日程就在视觉上变为置灰并加删除线,同时底部统计数字会更新,形成正向反馈。

@Component struct ScheduleRow { item: ScheduleItem; onToggle: () => void; build() { Row({ space: 12 }) { Button(this.item.done ? '✓' : '〇') .width(36) .height(36) .backgroundColor(this.item.done ? '#4CAF50' : '#E0E0E0') .onClick(() => { this.onToggle(); }) Column() { Text(this.item.title) .fontSize(22) .decoration({ type: this.item.done ? TextDecorationType.LineThrough : TextDecorationType.None }) .fontColor(this.item.done ? '#999999' : '#333333') Text(`${this.item.date} ${this.item.time}`) .fontSize(14) .fontColor('#999999') } .alignItems(HorizontalAlign.Start) .layoutWeight(1) } .width('100%') .padding(12) .backgroundColor('#F7F7F7') .borderRadius(12) } }

状态切换的逻辑放在父组件里,通过toggleDone方法从前到后遍历列表,找到对应 id 后把done字段取反。这样子组件不需要自己维护可变数据,状态统一由父组件管理,结构更清晰。实际测试中我发现,当列表项变多时,如果状态分散在子组件内部,很容易出现“界面没刷新但数据变了”这种幽灵问题,统一状态管理可以从源头上避免。

3.4 本地持久化:首选项与关系型数据库怎么选

日程数据必须本地保存,这涉及到一个选型问题:鸿蒙提供了多种本地存储方案,首选项(Preferences)和关系型数据库(RDB)是最常用的两种,到底选哪个?

我的选择是首选项,原因很直接:宝贝日程表的数据量很小,一个家庭一天的日程最多几十条,用关系型数据库属于杀鸡用牛刀。首选项的特点是轻量、结构简单、适合 key-value 形态的少量数据,我的做法是把整个日程数组序列化成 JSON 字符串,存到一个 key 下面。读取时反序列化,写回时整体覆盖,逻辑非常简单。

import { preferences } from '@kit.ArkData'; const PREF_NAME = 'schedule_store'; const KEY_LIST = 'schedule_list'; async function loadList(context: Context): Promise<ScheduleItem[]> { const store = await preferences.getPreferences(context, PREF_NAME); const json = store.getSync(KEY_LIST, '[]') as string; return JSON.parse(json); } async function saveList(context: Context, list: ScheduleItem[]): Promise<void> { const store = await preferences.getPreferences(context, PREF_NAME); await store.putSync(KEY_LIST, JSON.stringify(list)); await store.flush(); }

这段代码几乎可以无脑复用。唯一要注意的是flush方法必须调用,否则数据只停留在内存里,应用异常退出就丢了。我第一版就漏了这一步,导致每次杀进程之后数据全没,排查了半天才反应过来。

那什么时候应该用关系型数据库呢?如果后续你要做历史日程按天查询、跨月统计、模糊搜索,数据量上来之后,JSON 整体读写的方案就不太合适了。那种场景建议切换到 RDB,建一张日程表,用 SQL 查询。但对于当前项目,首选项是性价比最高的方案。

4. 构建、签名与真机调试

4.1 构建 HAP 产物:hvigor 构建链路

功能代码写完,第一件要做的事是构建出可以在真机上安装的 HAP 包。HAP 是鸿蒙应用的分发物,相当于 Android 的 APK。执行构建只需要一条命令:

./hvigorw assembleHap

首次构建会比较慢,因为要拉取配套的工具链依赖。构建完成后,产物一般在entry/build/default/outputs/default/entry-default-unsigned.hap。注意这个文件名的unsigned标记,它说明当前包还没有签名,直接装到真机上会被系统拒绝。

hvigor 的构建链路其实分几个阶段:资源编译、ArkTS 编译到字节码、资源索引生成、打包、签名。其中任何一个环节出错都会中断整个流程。我项目里遇到的坑主要是在资源层面,比如往resources目录里塞了一张命名字段不合法的图片,构建直接报错退出,排查方法是把新加的资源文件逐个移除做二分排查,很快就能定位。

4.2 签名配置与真机调试

真机调试绕不开签名。鸿蒙应用签名涉及两套体系:调试签名和发布签名。调试签名用于开发阶段在真机安装运行,发布签名用于上架市场。先用调试签名把应用装到真机上跑通,再把发布签名配好做上架包。

签名配置分三步走:生成密钥库文件(.p12)、生成证书请求(.csr)、申请调试 Profile(.p7b)。这些操作在 DevEco Studio 的 Project Structure 界面里可以半自动完成,也可以在 AppGallery Connect 后台手动操作。我的建议是:第一步在本地用keytool命令生成密钥库,后续步骤在 AGC 后台完成。

调试 Profile 申请好之后,把它和密钥库文件放到工程的entry/signature目录下,然后在build-profile.json5里注册签名信息:

{ "modules": [ { "name": "entry", "signingConfigs": [ { "name": "debug", "material": { "certpath": "./signature/debug.p7b", "storePassword": "123456", "keyAlias": "debug", "keyPassword": "123456", "profile": "./signature/debug-profile.p7b", "signAlg": "SHA256withECDSA", "storeFile": "./signature/debug.p12" } } ] } ] }

配置完成后再跑一次assembleHap,输出的产物名会从unsigned.hap变成正式的 HAP 包。这里提醒一句:storePasswordkeyPassword虽然是写在工程里的,但千万不要把这个文件提交到公开仓库,否则别人可以拿你的签名做应用分发。签名的安全性直接关系到应用的身份可信度。

连真机调试还用到另一个命令工具hdc,它是鸿蒙的设备连接工具,类似 Android 的 adb。常用命令也不多:

hdc list targets hdc install entry-default-signed.hap hdc shell

新增设备第一次连接时,手机上会弹出授权确认框,点击允许后hdc list targets才能看到设备。如果总是看不到设备,优先检查开发者模式有没有打开、USB 调试有没有授权,这两个环节最容易出问题。

4.3 构建报错排查实录

这个项目开发周期不长,但构建报错遇到好几个,挑三个有代表性的记录下来,都是大家大概率会碰上的。

第一个是签名相关:error: verify signature failed。出现这个报错,绝大多数情况是 Profile 证书和密钥库不是一套。我在调试阶段申请了好几个 Profile,混乱之中配混了,导致 hvigor 签名之后自校验不过。解决方法是把signature目录下的文件清理干净,重新申请一套调试签名配置。

第二个是依赖相关:ohpm install超时或者拉取依赖失败。鸿蒙的 ohpm 仓库是分地区的,网络不好的时候很容易失败。我的经验是设置镜像源,在~/.ohpmrc里配置国内镜像地址,速度会提升很多。另外,仓库里部分三方库的版本兼容性参差不齐,引入之前最好先查一下它支持的 API 版本和鸿蒙 SDK 版本。

第三个是资源编译相关:resource path is invalid。这种情况大多数是因为资源文件名称不规范或者放错了目录。ArkUI 资源文件要求小写命名,不支持特殊字符,即使是一个圆点也可能导致构建失败。发现问题后不需要改动代码,把资源名改成ic_schedule_add.png这种规范格式重新构建即可。

5. 上架全流程与避坑指南

5.1 AppGallery Connect 后台配置

功能稳定、本地测试通过之后,就可以准备上架了。鸿蒙应用上架主要走 AppGallery Connect(简称 AGC)平台。登录 AGC 后台后,第一步是创建应用,填入应用名称、包名等基础信息。这里的包名必须和工程里AppScope下的配置保持一致,否则后面签名和审核都会出问题。

创建好应用之后,需要在“开发服务”里申请发布证书和发布 Profile。这个流程和调试签名类似,但注意不要把调试签名和发布签名搞混。发布证书申请时需要用到本地的 CSR 文件,这个文件在 DevEco Studio 的 Project Structure 里可以生成。整套证书申请流程大约几分钟就能完成,但审核可能需要等待,所以建议提前准备。

证书和 Profile 拿到后,在工程里新增一个releasesigningConfigs,然后把构建命令改成生成发布包。hvigor 会根据当前构建类型自动选择合适的签名配置。

5.2 隐私合规与权限声明

现在应用市场上架对隐私合规要求相当严格,鸿蒙这边也不例外。宝贝日程表功能简单,不需要电话、存储、定位这类敏感权限,这是先天优势。但即使不申请权限,也需要在 AGC 后台提供一个隐私政策网址,并且在应用详情页里如实填写 App 收集哪些数据。日程类应用如果完全没有网络功能,数据仅存本地,隐私政策可以写得很简单,但如果不提供,审核很大概率会被打回。

另外有一个容易被忽视的点:如果 App 里用到了网络能力却不上报隐私政策,或者隐私政策声明的内容与实际功能不符,审核人员可能会以“隐私合规风险”为由拒绝。我自己在初次提交时就因为隐私政策链接写成默认占位网址被打回一次,改正确之后很快就通过了。

权限声明这块我的建议是:能不用权限就不用权限,这是最稳的合规策略。日程列表要做到单机可用,完全不需要任何联网类权限,反而更符合轻量工具类应用的定位。

5.3 提交审核与常见拒绝理由

应用信息全部填好、发布包构建完成之后,就可以在 AGC 后台上传了。上传 HAP 包之后,后台会解析包信息,校验签名、版本号、图标尺寸等基础项。如果这里就报错,一般是包名不一致或签名 Profile 用错环境,解决起来也直白:重新确认签名配置,重新构建。

我整理了审核阶段最容易踩的几个坑:

常见问题表现解决办法
隐私政策缺失后台提示未提供隐私政策补充可访问的隐私政策页面
应用截图尺寸不对上传截图提示分辨率不符按后台要求重新截图
功能描述与实际不符审核发现 App 内没有描述的功能保持描述简洁,不夸大功能
版本号不合法版本号小于已上架版本每次发版递增 versionCode

审核周期一般来说是几天不等,看提交时间段和排期情况。收到审核意见后,按意见逐条修改再重新提交即可,不用紧张。

6. 项目总结与后续扩展思路

6.1 从这次实战中学到什么

做完这个项目,一个很直接的感受是:鸿蒙开发的上手门槛没有想象中高,但构建和上架环节的细节确实比预期要多。DevEco CLI 的价值在这次项目中体现得很充分,从建工程到出包几乎不用打开 IDE,而且每个构建步骤都可追溯,出了问题能快速定位到具体环节。对于想批量做工具类 App 或者搭自动化流水线的团队来说,命令行这条路值得尽早投入。

另外一个体会是关于“小项目也要认真对待流程”。宝贝日程表功能很简单,但签名、隐私政策、版本管理这些环节一个都不能少。早早在工程里把调试签名和发布签名分开配置,后面做版本迭代会非常省心,不需要每次发版都临时去折腾证书。

6.2 还能往哪些方向扩展

这个 App 后续可以扩展的方向确实不少。如果能引入日历控件,就能从“只看今天”升级为“随便翻哪天的日程”,这对周计划和月计划的场景是刚需。再进一步,如果要做多设备同步,可以在鸿蒙的分布式数据管理能力上做文章,让手机和平板之间共享同一份日程数据。还有家长控制功能,比如设置一个简单的四位数密码,防止孩子自己乱改日程,这类功能在儿童用品的场景下会很受欢迎。

不过这些都是后话。第一版先把基础体验做扎实,孩子能每天打开列表,看懂今天要做什么,按完打卡有成就感,这个项目的核心价值就已经到位了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询