开头稍微有点长,先把自己放进场景里:
如果你跟我一样,既想给自家孩子做一款真正能用的日程管理工具,又想借着鸿蒙这波浪潮练手,那么“用命令行跑通一个鸿蒙 App 从开发到上架”这件事,绝对值得认真做一遍。我这次做的「宝贝日程表」就是这样一个项目,按家长视角拆需求,用 DevEco CLI 从空目录初始化工程,用 ArkTS 写界面和业务逻辑,最后打包成 HAP 提交到华为应用市场。整个过程没有靠 IDE 的图形向导一步步点,而是尽量用 hvigor、ohpm 这些命令行工具链完成,好处是逻辑清晰、可复现,后期接 CI 也顺手。这篇文章会把我从立项、开发、签名打包到送审上架踩过的坑和沉淀下来的方法完整展开,适合正在学鸿蒙开发、或者准备做正式上架产品的人参考。
1. 项目定位:为什么是「宝贝日程表」,为什么选 DevEco CLI
1.1 需求拆解与产品边界
「宝贝日程表」这个名字听起来很家常,但背后是一个很典型的效率工具需求:家长需要给孩子安排起床、吃饭、学习、运动、睡眠等固定事项,同时记录实际完成情况,慢慢帮孩子养成习惯。市面上的儿童日程 App 不少,但普遍有两个问题:一是广告和付费墙太多,二是数据模型过于复杂,孩子自己看不懂,家长每次都得替孩子操作。
所以我在拆需求时给自己定了三条硬约束。第一,界面要足够大,按钮要足够大,核心操作必须能在 3 步以内完成,因为使用者很可能是四五岁的小朋友。第二,数据要本地化优先,不强制注册账号,不搞云同步,避免个人信息合规上的麻烦。第三,所有功能必须离线可用,早教场景里经常没有稳定的网络。基于这些约束,我把「宝贝日程表」的产品边界锁定为三个模块:日程模板管理、每日打卡记录、完成情况看板,不做社交、不做消息推送、不做付费订阅。
模块一,日程模板管理。家长可以按周一至周日分别设置时间块,比如“7:30 起床”“8:00 吃早饭”“16:00 户外活动”,每个时间块可以选一个 emoji 图标和颜色,方便不识字的低龄儿童识别。模块二,每日打卡记录。当天到了某个时间点,孩子或者家长在首页点一下对应卡片,就完成一次打卡,后台会记录实际打卡时间戳。模块三,完成情况看板。按周维度展示每个事项的完成次数和完成率,用最简单的柱状图呈现,家长能快速知道本周哪些习惯执行得好,哪些需要调整。
1.2 DevEco CLI 是什么,我为什么没用纯 IDE 向导
很多初学者接触鸿蒙开发,第一反应是打开 DevEco Studio 点“New Project”,然后一路 Next。这个流程没有错,但如果你打算把工程交给 Git 管理、在 CI 上自动构建、甚至让团队成员用不同系统环境协作,纯 IDE 操作就不够透明了。DevEco CLI 并不是指某一个单独的命令,而是 DevEco Studio 配套命令行工具链的统称,核心包括 hvigor(构建引擎,类似 Gradle)、ohpm(包管理器,类似 npm 管理三方库)、SDK Manager(管理 API 版本和 SDK 组件)以及签名、打包相关的工具。
我用命令行方式,最核心的原因是可以把整个构建过程写进脚本,任何一步出错都能从日志里看到具体是哪个配置项的问题,不用反复在 IDE 界面里寻找菜单。比如初始化空工程,我只用一条命令创建目录结构,然后手动维护build-profile.json5和module.json5,这样我对每一个字段的作用都非常清楚。后面接自动化打包的时候,只需要按顺序执行“更新版本号 -> 编译 HAP -> 签名 -> 校验产物”这几条命令。而且 DevEco Studio 本身就是建立在同样的命令行工具之上,所以最终产物质量和你用 IDE 构建是完全一致的。
2. 从空目录到第一个 HAP:环境搭建与脚手架
2.1 工具链全景:Studio、hvigor、ohpm 各自负责什么
在开始之前,先理清工具链的层级关系。DevEco Studio 是 IDE,它的底层是 DevEco 命令行工具包,包含 SDK、hvigor 和 ohpm。hvigor 是鸿蒙的构建工具,读取build-profile.json5、oh-package.json5和模块里的module.json5,负责编译资源、生成中间产物、最终打出 HAP。ohpm 则是三方库管理器,类似 npm,负责拉取类似于@ohos/axios、路由库、UI 组件库等依赖,并把它们链接到工程里。还有一个容易被忽略的是 SDK Manager,鸿蒙 SDK 分为Default、HarmonyOS NEXT等多个版本,你可以用命令行查看和管理已安装的 API 版本。
这一层搞清楚以后,构建的整个脉络就通了。你写的是 ArkTS 源码和资源文件,ohpm 负责把依赖装进本地oh_modules,hvigor 拿着 SDK 的编译器做类型检查和编译,最后输出 HAP。如果你还配了混淆,hvigor 也会在编译阶段做代码压缩混淆。
2.2 创建工程与最小可运行版本
我用命令行从零创建工程的过程是这样的。先新建一个空目录,比如baby-schedule,在里面创建oh-package.json5作为工程级配置。这个文件类似根package.json,里面声明依赖和工程信息,比如"modelVersion": "5.0.0"表示使用 Stage 模型 5.0 版本。然后创建build-profile.json5,指定app的signingConfigs、products,以及模块列表。
模块是我们真正写代码的地方。在entry/src/main下面,我手动创建了几个关键文件:
module.json5:模块配置,声明入口 Ability、权限、支持的设备类型。ets/entryability/EntryAbility.ets:应用入口 Ability,负责加载页面并管理生命周期。ets/pages/Index.ets:首页,也就是日程列表页。resources/base/profile/main_pages.json:页面路由表,声明所有页面路径。resources/base/element/string.json:字符串资源。
这些文件建好之后,执行ohpm install安装基础依赖,再执行hvigorw assembleHap --mode module -p product=default就能打出第一个 HAP。第一次跑构建的时候大概率会遇到几个小问题,最常见的两个:一是hvigorw找不到,那是因为你还没把 DevEco Studio 里自带的hvigor脚本路径加入 PATH,或者在工程根目录缺少hvigorfile.ts;二是oh-package.json5里没有声明@ohos/hypium之类的框架依赖,测试相关模块编译不过。这些都属于环境问题,按日志提示补全即可。
注意:鸿蒙工程的
module.json5里deviceTypes不能乱写,比如只列了phone,却想在平板上运行就会出现安装失败。我建议一开始就填["phone", "tablet", "2in1"],这样三端都能装,虽然要额外留意 UI 适配,但从长期看省事。
2.3 目录结构里的“雷区”:没被 ide 自动生成的坑
用命令行建工程,有一件事和 IDE 向导差别很大:IDE 会自动帮你生成EntryAbility的注册、路由表映射、还有各种资源引用,但手动建工程时这些都要自己检查一遍。最常见的坑是你们在main_pages.json里写了某个页面路径,但实际对应的.ets文件不存在,编译会报“找不到页面”;反过来,你把页面文件放在pages/下面但没在main_pages.json注册,编译不会报错,但运行时跳转会黑屏。所以每新增一个页面,我习惯立刻把它加进路由表,并跑一次编译,而不是攒在一起改。
另一个容易忽略的是资源目录。ArkTS 里引用字符串资源用$r('app.string.xxx'),如果你在string.json里删除了某个键,但代码还在用,这个不会在编译期报错,而是运行到该页面时白屏或异常。为此我专门养成了一个习惯:资源索引的增删一定和代码改动同步,并且在提交前全局搜一遍$r(引用。做命令行开发没有 IDE 的实时错误提示,这些自检动作必须养成肌肉记忆。
3. 核心功能开发:ArkTS 写界面与状态管理
3.1 Stage 模型与 ArkUI 页面结构
鸿蒙应用从 HarmonyOS NEXT 开始全面推行 Stage 模型,和旧的 FA 模型相比,最大的变化是模块化更清晰:一个应用可以有多个 Module,每个 Module 可以包含多个UIAbility和页面。我用 Stage 模型的思路组织「宝贝日程表」,实际上就是让 UI 和业务逻辑解耦。
首页Index.ets的结构我设计成上下两个区域。顶部是日期切换栏,左右箭头切换当周,中间显示“x月x日 周x”,底部用一条分割线;下方是日程卡片列表,List组件里面嵌套ListItem,每张卡片显示时间、图标、标题、完成状态,以及一个大大的打卡按钮。为了让孩子能看懂,我把整个页面背景色做成淡黄色,卡片做成圆角白底,未完成事项的按钮是灰色,点击后变成绿色并把“打卡”换成“已完成”。在 ArkUI 里实现这整套布局大概只需要Column、Row、List这几个核心容器,代码量不大,但状态变化比较多。
3.2 状态管理:@State、@Prop、@Link、AppStorage 怎么选
ArkTS 基于 ArkUI 的状态管理机制,理解这一块基本就理解了整个前端开发模型。我写了一个日程项组件ScheduleItem,父页面持有当前选中星期的日期列表数据,子组件负责展示单条日程。这里面用到了几个关键装饰器:
@State:父页面持有数组,比如@State scheduleList: ScheduleModel[],当数组内容变化时,UI 自动刷新。@Prop:子组件接收父组件传入的单一值,比如@Prop item: ScheduleModel,但要注意@Prop是单向同步,子组件修改它不会同步到父组件。@Link:如果子组件需要修改父组件的数据,就要用@Link做双向绑定。我最开始把打卡按钮的点击事件写在子组件里,直接改@Prop的字段,结果发现父页面视图不刷新,查了半天文档才想起来要改成@Link。AppStorage:跨页面共享的全局存储,我在首页点击打卡后,需要让统计页立刻感知数据变化,就把当前累计打卡数放进了AppStorage,统计页@StorageProp接收后自动刷新图表。
简单地说,一个页面内部用自己的@State,父子之间用@Prop单向传值、@Link双向联动,跨页面共享用AppStorage。这个选择模型在小型项目里完全够用,不需要引入额外状态管理库,代码也好维护。
3.3 数据持久化:Preferences 还是关系型数据库
日程打卡类 App 对持久化的要求其实比想象中高。需要保存的数据有两类:一类是“日程模板”,数量少、结构固定,比如每周每天的固定事项;另一类是“打卡记录”,会随着时间增长越来越多,而且需要按日期、按事项维度做统计。
我在第一个版本里只用了@ohos.data.preferences(Preferences),它是一个类似键值对的轻量存储,存日程模板绰绰有余。但到了上统计功能的时候,发现打卡记录用 Preferences 存非常痛苦:要统计“这周星期一早上 8 点的事项完成了没有”,我得把所有记录遍历一遍,边遍历边解析 JSON。后来我改成了关系型数据库 RDB(Relational Database),用@ohos.data.relationalStore建了两张表,一张schedule_template,一张checkin_record。
建表语句我写在了一个独立的database.ets里,用CREATE TABLE IF NOT EXISTS保证重复执行不报错。打卡时插入一条记录,统计时用SELECT COUNT(*) WHERE schedule_id=? AND date BETWEEN ? AND ?就能拿到完成数,查询效率比遍历 Preferences 高太多。所以我的建议是:固定配置、用户偏好用 Preferences,凡是会增长、需要筛选统计的数据,直接上 RDB,不要图省事。
注意:RDB 的使用要处理好数据库实例的获取时机。在 Ability 的
onCreate里初始化数据库连接,然后用单例管理RdbStore,避免每次页面访问都重新打开。还有,所有数据库操作默认是异步接口,如果直接await放在aboutToAppear里,要注意页面可能先渲染再等数据,最好做一个 Loading 状态。
3.4 权限配置与隐私合规:儿童类 App 要格外小心
「宝贝日程表」功能简单,理论上不需要太多权限。但我遇到过很多同行在这里翻车,原因是觉得自己没申请敏感权限,就不需要写权限说明,结果上架审核时被要求补充“权限使用说明”。我的做法是:在module.json5里最小化声明,只保留了ohos.permission.INTERNET(因为需要反馈页面报错日志)和ohos.permission.STORE_PERSISTENT_DATA(持久化存储相关)。同时在 App 内设置页增加一个“隐私政策”入口,把采集什么、不采集什么、数据存哪里写清楚。
儿童类应用还有额外的要求:不能诱导儿童点击广告、不能收集儿童个人信息、必须有家长控制的内容。我直接在设置页加了“家长锁”,进入设置和统计数据查看前需要完成一个简单的两位数乘法验证,这样既能保护儿童不误触,也向审核人员展示了产品在儿童隐私上的谨慎态度。上架填写年龄分级时,我选择了“儿童适宜”,对应的资料要求也更严,但通过了之后应用商店会给产品一个很好的信任背书。
4. 打包与签名:从 HAP 到可安装交付
4.1 HAP、HSP、HAR 到底怎么选
做鸿蒙开发,工程产物有 HAP、HSP、HAR 三种,很多新手容易搞混。简单区分一下:
- HAP(HarmonyOS Ability Package)是应用最终安装包,一个应用由一个或多个 HAP 组成,入口模块的 HAP 是必须的。
- HAR(HarmonyOS Archive)是静态共享包,编译时会把代码和资源打包到 HAP 里,类似 Android 的 AAR 或者前端里的本地依赖。
- HSP(HarmonyOS Shared Package)是动态共享包,运行时由系统加载,多个 HAP 可以共用,减少重复代码体积。
「宝贝日程表」早期是一个单 HAP 工程,把工具函数、网络请求、数据库封装全放在entry里。后来为了结构清晰,我把通用的日期处理、颜色主题、数据库 helper 抽成了一个独立的commonHAR 模块,作为oh-package.json5里的本地依赖挂进工程。这样做的直接好处是,后续如果我再做第二个 App,可以直接复用这个 HAR,不用复制粘贴代码。
至于要不要做 HSP,要看场景。如果你的应用有独立的大体积功能模块(比如视频播放、AI 模型下载),可以考虑拆成 HSP 并在需要时动态加载。对「宝贝日程表」这种轻量工具类应用,单 HAP + 一个公共 HAR 已经足够,过度拆分反而会增加管理和调试成本。
4.2 签名证书的完整链路:p12、cer、p7b、profile 到底是谁
签名是上架前最容易卡住的地方,很多人分不清.p12、.cer、.p7b和.profile的关系。我用一句话总结:.p12是你的私钥和公钥证书文件,类似于你的“数字身份”,里面包含私钥,绝对不能泄露;.cer是华为开发者证书,证明你是合法的开发者,由 AGC 签发;.p7b是证书链文件,是一个包裹多个证书的容器;.profile(Provisioning Profile)则是一个授权文件,里面声明了你这个应用包名能用哪些证书签名、能在哪些设备上安装。
开发阶段可以用 DevEco Studio 的自动签名功能一键申请。但如果你像我一样主要用命令行,可以在 AGC 后台手动创建证书和应用签名,然后把signingConfigs写进build-profile.json5。命令行签名时,本质上就是调用签名工具,把私钥和 profile 绑定到 HAP 上,生成签名后的包。我建议把.p12密码写进本地的环境变量,而不是直接写死在build-profile.json5里,否则工程一旦开源,私钥泄露等于身份被盗用。
我这里踩过一次大坑:在 AGC 后台创建应用时包名填的是
com.example.babyschedule,但工程module.json5里的bundleName写成了com.example.babyscheduler,结果签名工具一直报“signature verification failed”,排查了很久才发现是包名不匹配。签名链路上一个字母都不能差。
4.3 多设备适配与屏幕适配
鸿蒙系统的设备形态很多,手机、折叠屏、平板,甚至车机、手表,同一个 HAP 要尽量在目标设备上都有好的体验。「宝贝日程表」一开始主要在手机上跑,后来我在折叠屏预览器上看了下,发现页面被拉得很宽,卡片间距也不协调。后来我加入了 GridRow/GridCol 响应式布局,把内容区域在宽屏下限制为最大 600vp 居中,两侧留白,看起来就舒服很多。
字体和图标也需要注意。华为手机默认字体大小可能被用户调得很大,如果你的 UI 固定写死字号,就会导致文字溢出。我给首页日期标题设置的是fp单位,并且最小字号限制为 14fp,最大 20fp,配合maxLines和textOverflow做兜底,确保极端字体下也不会乱掉。这套适配做得越早,后面上架审核被退回“界面显示异常”的风险就越小。
4.4 编译过了但运行时崩的三个典型原因
命令行编译只要报“BUILD SUCCESSFUL”,只能说明语法和资源引用没问题,运行时崩溃往往源于配置或生命周期问题。我在这类小应用上遇到过三个典型原因。
第一,页面没有注册路由表。新增统计页Statistics.ets后忘记加入main_pages.json,编译正常,但点击入口时页面直接崩掉,日志里会显示router.pushUrl找不到目标。第二,aboutToAppear里异步操作没处理好。我在这个生命周期里去查数据库,结果拿到数据时组件已经销毁了,赋值给@State就报错。后来我用if (this.isPageActive)这种标志位做保护。第三,在子组件里直接修改@Prop对象属性。前面说过@Prop是单向的,虽然编译期不会报错,但行为不符合预期,看起来像“数据改了 UI 没刷新”,实际是数据只在子组件内部改了,父组件没感知。
这些崩溃在 IDE 里其实都有比较明确的日志,但如果你只在命令行构建,没有 DevEco Studio 的调试器,就要学会在关键生命周期打日志,用hilog.info输出关键变量,暴力定位问题。
5. 正式上架:AGC 后台与送审清单
5.1 上架前需要准备哪些材料
当你的 HAP 在真机上跑得足够稳定,把“上架”提上日程时,首先要意识到上架不只是传一个包那么简单。我整理了一个材料清单,照着准备就不会漏:
- 开发者账号:在 AppGallery Connect 完成企业或个人实名认证。个人开发者可以上架,但应用市场对个人应用会有一些额外的审核询问。
- 隐私政策:必须有一个可访问的 URL。我用的是腾讯云的一个静态页面,里面详细写了这个 App 不采集个人信息、数据仅存储在本地、不含第三方广告 SDK。
- 用户协议(可选但强烈建议):说明应用功能与使用规则。
- 应用图标与宣传图:要求 PNG/JPG 格式,尺寸至少 512x512,宣传图需要和真实 UI 尽量一致。
- 版本说明:简要描述新版本功能和更新点。
- 测试账号:如果应用有登录功能,需要提供测试账号给审核人员;「宝贝日程表」没有登录,所以我额外强调了“无需账号即可体验全部功能”。
儿童类 App 还要额外准备“儿童隐私保护声明”,在年龄分级选择“儿童”之后,系统会强制要求上传。我建议在开发期就把这个声明写好,而不是等审核被拒再补。
5.2 在 AGC 创建应用和上传构建包
在 AGC 后台的流程其实很清晰:创建项目 -> 创建应用 -> 填写包名和基础信息 -> 配置签名证书 -> 上传 HAP -> 填写版本信息 -> 提交审核。
这里的核心问题是包名和签名必须和本地一致。包名就是你工程里的bundleName,AGC 创建应用时一旦生成不可修改。签名证书方面,你可以回到“用户与访问”里创建一个应用签名证书,证书指纹要和你本地.cer里的指纹一致。如果指纹不一致,上传 HAP 时会出现“证书不匹配”的错误。
上传 HAP 时,AGC 会让你选择支持的设备类型,这里建议和module.json5里的deviceTypes保持一致。然后填写版本说明和上架截图。截图这块我有个教训:第一次送审我偷懒用了模拟器截图,结果被以“界面截图与真实设备存在差异”退回。后来我改用真机截图,宽度、状态栏、刘海屏适配都跟真实用户看到的一致,一次就过。不要低估审核人员的严谨程度。
5.3 审核被退回的常见理由和应对方式
从我自己的经历和身边朋友的反馈,鸿蒙应用市场审核被退回的高频理由大概是这几类:
- 隐私政策链接无效或内容与实际权限不符。
- 权限申请理由不充分,比如一个日历应用申请位置权限。
- 应用内出现“测试”“内测”等字眼,或存在明显的体验问题。
- 截图与应用实机效果不一致。
- 应用图标不符合设计规范,比如背景透明导致图标一片黑。
我正式提交之前做了一次完整的自测清单:把 HAP 重新签名安装到一台完全没装过的手机上,从桌面图标点进去,走一遍所有页面;确认无账号也能正常使用核心功能;截图务必是这台真机在同一版本上的截图。然后才在 AGC 上提交。第一版审核用了大概两个工作日就通过了,我自己写的一个小工具类应用能达到这个效率,说明只要前期把合规和体验做扎实,没必要怕审核。
6. 避坑记录:一套命令行开发者的常见问题速查
6.1 构建与工具链问题速查
我在整个开发过程中遇到的问题不少,这里按类别整理成表格,方便你直接检索。
| 问题现象 | 大概率原因 | 解决办法 |
|---|---|---|
hvigorw: command not found | hvigor 脚手架脚本未安装,或不在 PATH | 在 DevEco Studio 的安装目录下找到 hvigor,或执行本地脚本./hvigorw |
ohpm install超时或失败 | 仓库地址配置错误、网络受限、缓存损坏 | 检查.npmrc的 registry 设置,删除oh_modules后重试 |
| 编译报错“router.pushUrl failed” | 页面没有在main_pages.json中注册 | 打开main_pages.json,补上对应页面路径 |
| HAP 安装到手机提示“签名不一致” | 手机的旧包签名与当前签名不同 | 卸载旧应用,重新安装新签名的 HAP |
| 运行后白屏但编译通过 | 资源引用错误,或页面生命周期里数据异常 | 使用 hilog 输出日志,检查$r()引用是否存在,检查 aboutToAppear 里的变量初始化 |
| 上架时提示证书指纹不匹配 | 本地证书和 AGC 后台录入的不一致 | 在 AGC 后台重新生成证书并同步本地签名配置 |
6.2 开发过程中“改到怀疑人生”的几次经历
这里再分享两个更细节的踩坑片段。
第一个是数据库连接没有及时关闭。早期版本我在每次插入打卡记录时都重新getRdbStore,结果频繁打开关闭导致偶现的“database is locked”错误。后来改成在应用启动时初始化一次,用一个单例类持有RdbStore,所有数据操作都走这个实例,锁冲突问题再没出现过。
第二个是关于状态管理的复杂数据流。我在统计页需要监听首页打卡后数据变化,一开始用AppStorage存一个全局计数,但发现统计页重新计算的时候要重新查数据库,单纯监听计数不够。后来我调整了思路:统计页每次onPageShow时主动刷新数据,而不是依赖全局变量联动。在鸿蒙里,onPageShow触发时机稳定,数据一定是最新的,处理统计页这种低频刷新场景反而是最简单可靠的方案。
7. 一些实战心得
「宝贝日程表」做完以后,我最大的感受是:鸿蒙开发工具链虽然还在快速迭代,但命令行这套玩法已经非常实用了。你能看到每个配置、每个构建步骤背后发生了什么,遇到问题更容易定位根因。而且用 DevEco CLI 结合脚本,我可以在不改任何代码的情况下,通过参数切换签名环境、版本号、构建类型,这让后期交付省了很多时间。
如果你也想跑一遍,我建议不要一开始就做复杂的 App,先拿一个单页面工具类应用练手。把“创建工程、写页面、接数据、打包、上真机、上架”这条链路彻底跑通一次,积累的经验比看十篇文档都有用。未来如果再加入自动化测试、多模块拆分,这套项目骨架也能平稳扩展。最重要的是,把产品打磨到能上架、能通过审核,这件事本身就是对开发者综合能力的一次全面检验。