鸿蒙App从命令行构建到上架全流程实战:宝贝日程表开发记录
我从一个很朴素的念头开始做这个项目家里小孩上小学之后课程表、兴趣班、作业截止时间全堆在一起口头提醒经常漏纸质的又容易丢。于是我用鸿蒙原生技术栈给自家孩子做了一个“宝贝日程表”从新建工程到最后在应用市场上架全程尽量少点鼠标把所有能脚本化的步骤都交给我本地的命令行工具——也就是常说的 DevEco CLI 这套工具链。整个过程踩了不少坑也把一套比较顺的链路跑通了今天完整记录下来给准备做鸿蒙 App、尤其是想走命令行工程化和上架流程的朋友做个参考。这套内容适合两类人一类是刚接触鸿蒙开发想搞明白一个 App 从零到上架到底要经过哪些环节的新手另一类是已经在用 DevEco Studio 写界面、但还没系统梳理过构建、签名、发布流程的开发者。我会把选型思路、核心代码、命令、踩坑记录都放出来尽量做到你照着做也能跑通。1. 项目概述与整体设计思路1.1 宝贝日程表到底要做成什么样认真想需求之前我先给这个 App 定了三条产品底线。第一必须快。孩子打开 App 到看到今天要做什么三秒内要完成所以首页不能有复杂的加载动画和数据请求所有数据优先本地化。第二必须直观。小学生认字有限界面不能堆文字要多用颜色、图标和大按钮。我最后把日程按“学习、运动、休息、兴趣班”四类做了四套配色首页用时间轴卡片展示一眼就能看清上午下午分别有什么安排。第三提醒要可靠。日程类 App 最核心的能力不是“记录”而是“到点提醒”。所以我专门用了系统的后台代理提醒能力来做通知而不是自己在前台跑定时器这样才能保证 App 退到后台甚至被清理后到点了依然能收到系统级通知。功能上最终收敛成四个模块今日日程时间轴、日程新增与编辑、按日期切换的日历视图、家长设置区。家长设置区里做了简单密码锁和提醒开关用来防止小孩自己乱改日程或关掉通知。1.2 命令行工具链的选型考量做之前我其实犹豫过直接开 DevEco Studio 不就行了为什么还要折腾命令行我的判断是这样的单机开发一个 demoIDE 完全够用但一旦牵扯到“可上架”“可交接”“可自动构建”这三个词命令行工具链就是绕不开的。原因有三点。第一IDE 的构建过程本质上是把命令行工具包了一层壳。你用 Studio 点一次“Build”底层跑的还是 hvigorw点一次“Sync”底层跑的是 ohpm install。既然绕不开不如直接掌握它遇到 IDE 缓存导致的各种诡异报错时反而好排查。第二命令行天然适合自动化。我这次要反复打 debug 包、签名、检查产物把这些步骤写成 shell 脚本后每次打包只需要执行一条命令中间不会有人为漏步骤的情况。后面如果要接流水线做持续集成这套命令也一样能直接搬过去。第三DevEco CLI 这套工具链是跨平台可用的。我平时在 macOS 上写代码但偶尔需要在 Linux 机器上出包命令行环境迁移成本几乎为零IDE 反而还要处理图形界面和授权问题。当然命令行不是万能的。UI 布局的实时预览、可视化调试这些最终还是得回到 DevEco Studio 里做。我的做法是写 UI 时用 IDE跑构建、搞依赖、出签名包时切回命令行。两者配合效率最高。2. 环境准备与工程初始化2.1 DevEco CLI 工具链构成在动手之前先把“DevEco CLI”到底包含哪些命令搞清楚。很多人以为它就是一个命令实际上是一套工具链我这次实际用到的有四个ohpm鸿蒙的包管理器类似前端的 npm负责拉取三方库和工程依赖。hvigorw构建工具类似 Gradle负责把 ArkTS 源码、资源文件编译打包成 HAP 包。hdc设备调试工具类似 ADB负责连接真机、模拟器安装和卸载 App、抓日志。hap-sign-tool.jar签名工具Java 编写的 jar 包负责给 HAP 包做签名也是上架前必经的一步。这四个工具在安装 DevEco Studio 时一般都会带上也可以单独下载 Command Line Tools 包具体路径取决于你的安装方式。我的建议是安装完成后把这个几个工具的路径加进 shell 的PATH环境变量后面用起来会顺手很多。# macOS 环境变量配置示例 export DEVECO_SDK_HOME$HOME/Library/Huawei/Sdk export PATH$PATH:$DEVECO_SDK_HOME/command-line-tools/bin配置完执行ohpm -v和hvigorw -v能正常输出版本号就说明环境没问题。2.2 从空文件夹到可构建工程用命令行创建一个全新的鸿蒙工程不像 IDE 里有“新建 Project 向导”那么可视化。我的做法是复制模板工程再改造这是目前命令行场景下最稳妥的方式。先从一个已有的标准工程目录开始把entry模块和build-profile.json5这些骨架文件保留然后统一改三个地方。第一是AppScope/app.json5里面的bundleName是整个应用的唯一标识上架之后不能随便改。我这次定的是com.example.babyschedule虽然示例域名不推荐商用但自己学习测试够用。{ app: { bundleName: com.example.babyschedule, vendor: example, versionCode: 1000000, versionName: 1.0.0, icon: $media:app_icon, label: $string:app_name } }第二是entry/src/main/module.json5配置模块的基本信息、入口页面和需要的权限。{ module: { name: entry, type: entry, srcEntrance: ./ets/entryability/EntryAbility.ets, requestPermissions: [ { name: ohos.permission.PUBLISH_AGENT_REMINDER } ] } }第三是工程根目录下的oh-package.json5里面声明依赖。这里要特别说明一下鸿蒙的依赖仓库源默认是华为的仓库如果ohpm install很慢或者超时可以手动配置镜像源。ohpm config set registry https://repo.harmonyos.com/ohpm/ ohpm install依赖拉取完成hvigorw就会自动执行相关任务。我先跑一个最基础的构建命令验证工程是否正常hvigorw assembleHap --mode module -p productdefault看到 BUILD SUCCESSFUL 之后说明这个空工程已经能出包了。接下来就可以开始写业务代码。3. 核心功能开发数据、界面与提醒3.1 数据持久化设计日程数据第一版我用了首选项来存简单是简单但很快发现不靠谱——首选项本质上是键值对存储不适合做需要按时间范围查询的列表数据。比如“查出 3 月 1 日到 3 月 7 日所有日程”这种操作用首选项要么全量读出来再遍历过滤要么用多个 key 拼接写起来很别扭性能也差。所以第二版我改成了关系型数据库。鸿蒙的ohos.data.relationalStore提供了完整的 SQLite 能力建表、增删改查都很顺畅。我定义的表结构很简单但足够覆盖核心场景CREATE TABLE IF NOT EXISTS schedule ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, category INTEGER NOT NULL, start_time INTEGER NOT NULL, end_time INTEGER NOT NULL, repeat_type INTEGER DEFAULT 0, remind_switch INTEGER DEFAULT 1, note TEXT );字段说明category用来区分学习、运动、休息、兴趣班四类对应不同的界面配色start_time和end_time存毫秒级时间戳repeat_type是重复规则0 表示不重复1 表示每天重复2 表示每周重复为后面做课程表循环日程留了扩展空间。在实际封装时我建了一个ScheduleDatabase单例类把建库、建表、增删改查都封装成异步方法。比如添加一条日程async addSchedule(item: ScheduleItem): Promisenumber { const store await this.getStore(); const values new relationalStore.ValuesBucket(); values[title] item.title; values[category] item.category; values[start_time] item.startTime; values[end_time] item.endTime; values[repeat_type] item.repeatType; values[remind_switch] item.remindSwitch ? 1 : 0; values[note] item.note; const rowId await store.insert(schedule, values); return rowId; }这里有个经验值得说一下数据库初始化一定要放在应用启动早期完成但建表操作不能阻塞主线程。我是在EntryAbility的onWindowStageCreate回调里异步初始化数据库首页加载数据前先确认 ready 标志位避免出现“界面出来了、数据还没准备好”的空白屏问题。3.2 ArkUI 页面与状态管理鸿蒙应用页面开发现在主推 ArkTS 和 ArkUI 声明式写法跟 Flutter 或者 SwiftUI 的体验有点像。核心思想是你声明界面长什么样数据变了界面自动刷新。首页我设计成上下两个区域顶部是一个横向滚动的日期选择条下面是当天日程的按时间排序列表。核心代码如下Entry Component struct HomePage { State selectedDate: number Date.now(); State scheduleList: ScheduleItem[] []; build() { Column() { DateBar({ selectedDate: this.selectedDate, onDateChange: (date) this.onDateChange(date) }) List({ space: 12 }) { ForEach(this.scheduleList, (item: ScheduleItem) { ListItem() { ScheduleCard({ item: item }) } }, (item: ScheduleItem) item.id.toString()) } .layoutWeight(1) } } }State是 ArkUI 的状态管理装饰器变量变化时依赖它的 UI 会自动重新渲染。我用State持有当前选中日期和列表数据切换日期时重新查数据库并赋值界面就会自动更新不需要手动操作 DOM。这里给新手一个建议列表项一定要给ForEach提供稳定的 key我用的是日程 id。如果不给或者用数组下标当 key刷新时很容易出现组件复用错乱的问题表现为列表项内容串位排查起来特别费劲。日程卡片用了Card组件卡片左侧有一条 6dp 宽的颜色条用category映射四种颜色这样孩子扫一眼颜色就知道是什么类型的安排。整个页面字体调大、按钮调圆基本就是给儿童使用的交互偏好。3.3 后台代理提醒的实现与避坑这是整个项目里技术含量最高、也最容易翻车的地方。我一开始试图在页面里用setInterval定时检查当前时间但很快发现这个方案根本不可靠App 退到后台后系统随时可能挂起定时器更别说用户主动从最近任务里划掉了。后来查文档发现鸿蒙提供了后台代理提醒能力也就是把提醒任务交给系统由系统进程在指定时间弹出通知。这才是日程提醒类 App 该有的姿势。核心代码长这样import reminderAgentManager from ohos.reminderAgentManager; async function createScheduleReminder(item: ScheduleItem): Promisenumber { const reminder: reminderAgentManager.ReminderRequestAlarm { reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_ALARM, hour: new Date(item.startTime).getHours(), minute: new Date(item.startTime).getMinutes(), daysOfWeek: [], title: 宝贝日程提醒, content: item.title, notificationContent: { title: 宝贝日程提醒, content: item.title }, ringDuration: 5, snoozeTimes: 2, timeInterval: 10 }; const reminderId await reminderAgentManager.publishReminder(reminder); return reminderId; }需要注意的是要使用这个能力必须在module.json5里申请ohos.permission.PUBLISH_AGENT_REMINDER权限而且这个权限属于常规权限不需要弹窗授权只要声明了就能用。这段代码里有三个我踩过的坑必须单独写出来。第一个坑是重复提醒。如果用户手滑点了一次“保存”我往数据库插了一条数据又往系统里注册了一个 reminder那用户就会收到两条一模一样的通知。正确做法是数据库里专门存一列reminder_id每次新增或修改日程时先把旧提醒删掉再注册新的。核心就是先删后建保证系统里的提醒与数据库记录一一对应。第二个坑是提醒不响。真机测试时我发现如果安装的是 debug 包且应用被用户手动“停止”过publishReminder的提醒就不会触发。后来排查文档才明白应用被强制停止后系统会把它标记为“已停止”状态直到用户再次主动打开 App后台代理提醒才会恢复。这是系统机制不是代码 bug但是测试时容易吓一跳。第三个坑是时区问题。ReminderRequestAlarm的 hour 和 minute 是按设备当前时区算的。如果用户改过系统时区或者有跨时区出差场景日程显示的时间和提醒触发时间可能对不上。稳妥的做法是注册提醒前先用系统 API 把时间戳转成设备本地时区的小时和分钟。4. 构建、签名与真机联调4.1 hvigorw 打包 HAP代码写完核心功能自测通过接下来就是把工程构建成可以安装和上架的 HAP 包。鸿蒙工程构建的本质是hvigorw根据build-profile.json5里的配置把 ArkTS 编译成方舟字节码再和资源文件一起打包。构建产物目录通常在entry/build/default/outputs/default/下面能找到一个.hap文件。debug 和 release 两种构建方式的区别主要在签名和混淆上。日常调试可以直接构建 debug 包用 debug 证书签名安装到真机跑上架前必须构建 release 包用 release 证书签名同时建议开启混淆。# 构建 release 包 hvigorw assembleHap --mode module -p productdefault --no-daemon加上--no-daemon是为了让构建在前台跑完就退出适合脚本场景。构建完成后我先检查一下 HAP 包大小ls -lh entry/build/default/outputs/default/一个带图片资源的日程 AppHAP 体积一般会在几 MB 到十几 MB 之间。如果发现体积异常大多半是 assets 里塞了不该塞的资源。我当时发现图标一套放了 5 种尺寸有的甚至不是应用内用到的删掉冗余资源后包体直接小了将近 2MB。4.2 签名与 Profile没有签名的 HAP 装不进真机更上不了架。这里要稍微解释一下鸿蒙的签名体系我当初第一次接触时也绕了很久。鸿蒙应用签名需要两套凭据证书和Profile。证书用来标识开发者身份由华为 AGC 平台签发Profile 描述这个应用具备哪些权限、支持哪些设备、使用哪个证书。用一个不严谨但好记的类比证书是你的身份证Profile 是盖章的通行证App 安装包两样都得带齐。签名方式有自动和手动两种。用 DevEco Studio 做自动签名最省事登录华为账号后 IDE 会自动生成调试证书和 Profile。但命令行场景下手动签名的步骤我得完整列出来因为上架的正式包必须走手动签名逻辑。手动签名的核心命令是用hap-sign-tool.jarjava -jar hap-sign-tool.jar sign-app \ -keyAlias release_key \ -signAlg SHA256withECDSA \ -mode localSign \ -appCertFile release.cer \ -profileFile release.profile \ -inFile entry-default-unsigned.hap \ -outFile entry-release-signed.hap这些文件的来源是在 AGC 平台上创建应用后配置并下载发布证书和发布 Profile。这里有个细节要特别提醒——Profile 和应用的 bundleName 必须一一对应。我说“必须”是因为这个错了签名阶段大概率不报错但一安装到真机上就会直接提示安装失败错误码ERR_APPEXECFWK_INSTALL_FAILED排查起来特别迷惑。4.3 hdc 真机调试构建产物有了签名也做了怎么装到手机上用hdc。鸿蒙手机的开发者模式默认是隐藏的需要在设置里连点“版本号”若干次才能开启。开启后插入 USB手机会弹出 USB 调试授权框。一切正常后用hdc list targets应该能看到设备序列号。安装命令hdc install entry-release-signed.hap想覆盖安装调试包hdc install -r entry-debug.hap想抓崩溃日志先hdc shell hilog过滤出当前应用的日志这一步对排查应用闪退特别有用hdc shell hilog | grep BabySchedule真机联调这个环节我最想强调的一点是不要只在模拟器上测完就算完。模拟器表现正常不代表真机表现正常。尤其是提醒、通知这些和系统能力强相关的功能模拟器和真机的行为差异很大。我的习惯是每次改完核心逻辑至少在真机上完整跑一遍“添加日程-锁屏-等提醒-收到通知-点击通知进详情”的链路确认没断才继续写下一个功能。5. 上架从本地 HAP 到应用市场5.1 开发者账号与实名认证上架前需要注册一个华为开发者账号并进行实名认证。个人开发者用个人身份认证就行流程很简单身份证信息加人脸识别几分钟就通过。企业开发者则需要营业执照等信息流程会长一些如果是公司项目要提前规划好时间。这里要特别说一句开发者账号一旦注册后面所有证书、应用记录都会挂在这个账号下而且证书有有效期过期了需要重新生成。我当时没注意证书有效期上线前重新签名折腾了半天这个教训写在这里。账号就绪后登录 AppGallery Connect 平台也就是 AGC后续的证书申请、应用创建、版本管理都在这里操作。5.2 创建应用与填写资料在 AGC 后台点击“创建应用”需要填的关键信息包括应用名称、应用包名、应用分类、语言、图标等。应用名称会展示在应用市场上和安装到手机桌面上显示的app.json5里的label不完全是一回事后者是桌面显示名。两者最好一致避免用户混淆。应用分类这里容易踩坑。我当时给“宝贝日程表”选的分类是“儿童”没想到审核人员反馈说这个分类需要额外提供儿童隐私保护说明。后来我重新审视了一下产品这个 App 主要使用对象虽然是孩子但它是家长配置日程、孩子查看信息严格说属于“日常生活”类工具换成“生活”分类后审核就顺利通过了。另外图标要求必须是 512x512 像素这个很多人都知道但截图尺寸和数量很多人会忽略。AGC 要求至少上传 3 张应用截图且分辨率需要覆盖主流手机屏幕比例。我刚开始只传了 2 张直接被驳回要求补图。5.3 上传与审核资料填完就可以上传已经签名好的 release HAP 包。上传的位置在 AGC 后台的“应用信息”-“版本管理”里上传后填写版本更新说明然后提交审核。审核周期在不同时期波动很大我等过最快的一天也遇到过一次拖了三四天。期间 AGC 会发邮件通知审核状态如果被驳回邮件里会写清楚原因后台也能看到审核意见。提审之前强烈建议自己在真机上把 HAP 装一遍完整走一遍核心流程。很多驳回原因不是技术问题而是“运行崩溃”“启动黑屏”“功能无法使用”这些基础问题。我一个朋友提审一个工具类 App因为没在真机测过结果首次启动申请敏感权限时崩溃直接被拒来回改了好几天。5.4 审核被拒的常见原因结合我自己的经历和圈内交流审核被拒主要集中在以下几类应用分类不准确尤其涉及儿童、健康、金融等敏感分类。缺少隐私政策链接凡是涉及用户信息收集的应用都必须提供。权限申请与功能不匹配比如一个日程工具不需要读取短信申请了就会被问询。应用截图与真实界面不符或者是用模拟器截的图布局变形。版本号问题versionCode 必须比上一个版本大否则无法提交。这些都不是功能问题纯粹是资料和合规细节但正是这些琐碎细节决定了上架流程顺不顺利。我自己的体会是提审前把 AGC 后台的每一项配置当成产品的一部分来对待宁可在后台多看几遍也别让自己在审核排队里耗时间。6. 问题排查速查与实战心得6.1 常见问题速查表整个开发到上架的过程中我记录了一批高频问题整理成表格放在这里方便以后直接搜现象可能原因解决方法ohpm install超时或拉不到依赖仓库源访问不稳定检查 ohpm registry 配置切换到可用镜像源构建报SDK component missingSDK 未安装完整用 DevEco Studio 的 SDK Manager 补装或用命令行检查 SDK 组件hdc list targets看不到设备未开启开发者模式/驱动未安装开启 USB 调试检查连接线重启 hdc server真机安装报ERR_APPEXECFWK_INSTALL_FAILED签名证书与 Profile 不匹配确认 release 包的证书和 Profile 来源一致重新签名签名时报certificate not yet valid系统时间偏差同步系统时间再执行签名提醒到点没触发应用被强制停止过/没有权限确认权限声明正确重新打开 App 后再测试表格里有些问题一眼看上去不是大问题但真遇到时最花时间。比如签名时报时间有效期不对我查了好半天才发现是测试机器时间慢了几天导致系统认为证书还没生效。6.2 几个让开发体验更好的小习惯项目做完我复盘了一下整个流程整理出几个今后写鸿蒙 App 一定会保留的习惯。第一所有构建、签名、安装命令脚本化。我把这次用到的命令整理成了一个build.sh里面做了严格的分步控制先拉依赖再构建再签名再安装。以后不管谁拿到这个工程跑一遍脚本就能出包省去口头沟通的麻烦。第二DataManager 层做统一封装。数据库、提醒、设置都通过统一入口调用模块之间不直接互相操作。比如“删除日程”这个动作在 Manager 层里会同时做三件事删数据库记录、删系统提醒、返回删除结果。这样上层页面只管调用不用关心底层联动逻辑代码清晰很多后期加云端同步也容易扩展。第三每轮迭代都重新做一遍全链路验证。哪怕这次改动只是改了一个按钮颜色只要影响了页面结构我就会重新打包、签名、安装然后走一遍核心路径。很多线上的坑其实在发布前只要多花十分钟做一遍主干流程就能提前发现。6.3 关于 DevEco CLI 的现状与展望最后聊几句我对 DevEco CLI 这套东西的整体感受。它现在的形态更像是一个“工具合集”还没有像 npm 对前端那样形成一套极致的标准工作流体验但已经能把一个应用从源码到上架包的链路完整串起来。对个人开发者来说它的价值不只是省时间更在于让整个构建过程透明化、可重复化这对于做技术复盘和问题定位特别重要。如果你是从 IDE 转命令行我建议不要急着全部切换。先在熟悉的 IDE 里写完功能再尝试用命令行构建一次看看产物目录里多了什么然后再试着用命令行安装到真机最后再挑战手动签名和上架。这样一步步过渡既不会因为陌生而挫败又能逐步掌握整条链路。这次“宝贝日程表”从产品想法到正式上架前后花了两周多的时间真正写业务代码的时间其实就三四天剩下的时间全消耗在构建、签名、资料审核这些“看不见”的环节上。但恰恰是这些环节决定了你的应用能不能体面地出现在用户面前。希望这篇记录能让你少走一点我走过的弯路。