用DevEco CLI纯命令行开发鸿蒙App:从初始化到上架全流程实战 📅 发布时间:2026/9/20 7:37:02 👁 浏览次数: 说实话决定用纯命令行DevEco CLI来开发一款鸿蒙 App在 2025 年看起来多少有点“反潮流”。大多数人打开 DevEco Studio 点几下鼠标拖几个组件就能建工程为什么我还要折腾终端原因其实很朴素我日常的主力编辑器不是 IDE而且我特别想把构建、打包、签名这一整套流程彻底跑通做到以后任何一台机器上都能自动出包。这篇文章要分享的就是我用 DevEco CLI 从 0 开发鸿蒙 App「宝贝日程表」一直走到正式上架的全过程。项目本身不复杂就是一个给家长记录孩子作息、课程、提醒事项的轻量应用但麻雀虽小五脏俱全从 ArkTS 状态管理、本地数据持久化到 hap 打包、AGC 上架该踩的坑一个都没落下。这篇文章适合三类人一是准备系统学习鸿蒙开发、但不想被 IDE 绑架的开发者二是已经写过几个 Demo、想知道 CLI 环境怎么独立构建和上架的人三是想找一个完整项目作为“面试作品”的求职者。我会把命令、代码、配置尽量贴全也会把我在真机和审核阶段遇到的问题原原本本列出来照着走大概率能少浪费两个周末。1. 为什么选 DevEco CLI 而不是 DevEco Studio1.1 命令行开发到底好在哪先说一个容易混淆的点DevEco CLI 并不是 DevEco Studio 的替代品它更像是藏在图形界面底下的那台“发动机”。Studio 里你每次点“同步”、“构建”、“运行”最终执行的都是 hvigor 和 hdc 这一套命令行工具。既然底层就是命令行那我直接用 CLI 操作反而少了很多中间层的等待和异常重试。对我个人来说选 CLI 还有一个非常实际的原因——可脚本化。宝贝日程表虽然是个小项目但后续如果要跑单元测试、做多版本构建、上传到自己的测试平台全部可以用 shell 脚本串起来。我一个脚本搞定同事只需要拉代码跑一条命令不需要安装完整 IDE也省得每个人本地的 Studio 版本不一样导致构建结果不一致。另外CLI 对编辑器的选择完全开放。我喜欢用 VSCode 配上一堆插件写 ArkTS代码补全和跳转一样能工作。项目大的时候开 IDE 要加载半天终端加编辑器的方式明显更轻。尤其是同时需要改前端样式和 ArkTS 逻辑的时候切换起来非常顺手。当然用 CLI 也要付出代价。最大的代价是官方文档默认你在用 Studio很多配置和命令要靠自己摸索遇到图形界面上一步能解决的问题命令行可能要多查好几层文档甚至要去翻 issue。这个适应成本是真实存在的但一旦把生态摸熟收益会超过成本。1.2 环境准备与 CLI 安装我的开发机是 macOSWindows 的流程也基本一致只是环境变量配法略有区别。第一步肯定是装 Node.jsDevEco CLI 完全基于 Node建议直接上 18 以上的 LTS 版本太老的版本会在 hvigor 执行时报出各种莫名其妙的错。我用的是 nvm 管理 Node在项目目录里用 .nvmrc 锁定版本避免几个月之后回来重新构建时因为 Node 版本不对而抓狂。接下来安装 CLI 本体。当前各版本安装命令略有差异推荐直接查官方文档里对应 SDK 版本的说明大致流程是通过 npm 全局安装 ohos/devco 这个包安装完执行 devco --version 确认版本号。有一点要特别留意CLI 的版本必须和本地 HarmonyOS SDK 的版本匹配就像 IDE 和 SDK 要对应一样版本不匹配时构建阶段会直接报 API 版本不支持这类错误。我自己就吃过一次亏用的是最新 CLI 搭配旧 SDK结果在 hvigor 编译期弹了一屏幕 version mismatch最后老老实实把 SDK 升上去才解决。装好 CLI 之后还要确认 hvigor 和 ohpm 这两个工具可用。hvigor 是鸿蒙的构建引擎负责把 ArkTS 源码编译成 hapohpm 则是鸿蒙的包管理器类似前端的 npm用来安装项目依赖。CLI 安装成功之后这两个命令一般会一起带出来但 PATH 可能要自己补一下。我在首次运行 hvigorw 构建项目时遇到了找不到命令的问题排查后发现是全局安装目录没有加到 PATH 里属于环境变量老问题补上就通了。2. 项目初始化与工程结构解读2.1 用 devco init 拉出一个可运行工程环境就绪之后初始化项目就非常简单了。在终端执行 devco init它会让你选择工程模板和应用名称。这里我建议第一次接触的人选一个最基本的“Empty Ability”模板不要一上来就选带侧边栏、底部 Tab 的复杂模板否则目录一堆代码看半天也不知道哪句是核心逻辑。初始化完成之后立刻执行同步依赖的命令我一般直接运行 hvigorw sync等它把 oh_modules 拉下来。这一步在 CLI 模式下是必须主动做的不像 Studio 会在打开工程时自动帮你完成。如果同步过程中提示缺少 oh-package.json5 里的某个依赖就去对应的 OpenHarmony 三方仓库搜包名用 ohpm install 补装。CLI 初始化出来的工程和 Studio 新建的工程结构几乎一模一样这也在意料之中毕竟底层是同一套模板。真正有价值的是理解每个文件是干嘛的否则后续改配置全靠猜。我习惯先把工程根目录完整列一遍对照官方结构说明把 build-profile.json5、oh-package.json5、hvigorfile.ts 这几个文件的角色搞清楚再动手写代码后面排错会快很多。2.2 搞懂 HAP、HSP、HAR 三种模块鸿蒙工程的模块分为三种这是整个项目里最值得理解清楚的概念因为后面所有包体积和依赖管理的决策都和它相关。HAP 是最终安装到手机上的应用包一个 App 至少有一个 entry HAP它包含了入口 Ability、页面资源和依赖的三方库 HAR 是静态共享包类似 Android 里的 library module编译的时候直接把代码和资源打进 HAP 里 HSP 则是动态共享包运行时按需加载适合把体积大、使用频率低的模块拆出去。我在宝贝日程表里的划分策略很简单entry 模块放页面和导航一个 common HAR 模块放日期工具函数、通用类型定义和主题常量。一开始我也犹豫要不要把“日程数据管理”做成 HSP毕竟后续有可能做平板适配和第二个 App 复用但纠结之后决定不拆。原因很实际第一版 App 总共才几个页面HSP 省下来的安装体积可以忽略不计但动态加载却会增加崩溃排查的难度等确实出现多 App 复用的需求了再拆完全来得及。三者的区别用一个表格总结比较直观模块类型打包方式加载时机适用场景包体影响HAP独立打包应用安装时应用入口、独立功能模块最终安装包HAR静态合入 HAP应用安装时通用工具库、基础组件打进宿主 HAPHSP独立打包运行时按需加载动态能力、独立大模块按需下载加载2.3 目录与构建脚本初读初始化出来的目录里最值得先读的是 build-profile.json5 和 oh-package.json5。build-profile.json5 负责描述模块的签名信息、编译选项和目标 API 版本以后配置签名的时候要反复动它oh-package.json5 则相当于 package.json里面列出了依赖的 HAR 包和本地模块引用我用它管理 common HAR 的依赖关系。module.json5 是另一个高频修改的文件尤其是其中 abilities 数组和 requestPermissions 数组。宝贝日程表需要用到通知权限所以在这里声明了 ohos.permission.NOTIFICATION_CONTROLLER 等权限注意不同 API 版本的权限名有差异以文档为准。另外页面路由配置也在 abilities 里pages 数组列出了所有可路由的页面路径新增页面之后漏加这个数组是初学者最常见的白屏原因。hvigorfile.ts 是构建脚本里面定义了 hap 任务和 har 任务生命周期其实不用懂太深但有个概念很重要HAR 模块的构建结果也可以单独产出 .har 文件后续做组件库分发时用得上。我第一周基本只看不动这几个文件构建出错时再回来看错误信息反而更快。3. 核心功能开发数据模型与状态管理3.1 日程数据模型设计宝贝日程表的核心是日程数据所以第一步先把数据模型定死。我用一个 interface 描述日程对象字段包括 id、title、date、startTime、endTime、tag、repeatRule、notifyEnabled、done、note。id 用时间戳加随机数生成字符串保证不重复date 存 YYYY-MM-DD 这样的字符串startTime 和 endTime 存 HH:mm 字符串这样 UI 展示时零转换排序时直接按字符串比较也行得通。刚开始我用 Date 对象存时间后来在写持久化时发现问题Preferences 只能存基本类型和可序列化的对象Date 对象序列化之后拿到的是一个字符串反序列化时还得手动 new Date()非常容易踩时区坑。改成纯字符串存储之后序列化和反序列化都干净了做日期比较和分组时写一个 formatDate 工具函数就能全局统一。ArkTS 对类型的要求比 TypeScript 严格一些对象字面量必须和 interface 完全兼容多一个字段都会编译报错这一点习惯了反而更好代码更稳。下面是一个基础的日程类型定义export interface ScheduleItem { id: string; title: string; date: string; // YYYY-MM-DD startTime: string; // HH:mm endTime: string; // HH:mm tag: string; // 课程/运动/就医/其他 repeatRule: none | daily | weekly | monthly; notifyEnabled: boolean; done: boolean; note?: string; }3.2 状态管理State、Prop、Link 的取舍页面逻辑里我用 State 声明日程列表和当前选中日期这是 ArkUI 最基础的状态装饰器变量变化时 UI 自动刷新。子组件需要展示某一个日程详情时用 Prop 把数据传下去需要反过来修改数据的场景比如把“已完成”勾选状态写回列表则用回调函数或者 Link。这里有一个我最初被坑过的细节State 装饰的数组如果直接调用 push 方法往里面添加数据UI 是能感知到的但如果你修改数组里某个对象的某个字段比如把一个日程对象的 done 从 false 改成 trueArkUI 的默认状态观测机制并不会触发刷新。解决方法是改完之后对数组整体重新赋值或者用 Observed 和 ObjectLink 组合来观测对象内部变化。我第一版图省事全用 State 加“重新赋值大法”后来发现页面稍微复杂一点就容易漏刷新干脆把列表页改成 Observed ObjectLink 的结构。具体来说用 Observed 装饰 ScheduleModel 类在子组件里用 ObjectLink 接收单个模型对象这样字段级别的修改能被 UI 立刻感知代码逻辑也更符合“修改即生效”的直觉。Observed export class ScheduleModel { item: ScheduleItem; constructor(item: ScheduleItem) { this.item item; } } Component export struct ScheduleListItem { ObjectLink model: ScheduleModel; // 直接修改 model.item.done 就能触发 UI 刷新 }3.3 本地持久化从 Preferences 到什么时候该上 SQLite日程数据的持久化我选择了 Preferences 这个首选项组件。它本质上是一个轻量级 key-value 存储适合存配置项和少量结构化数据。一个家庭正常使用一天最多也就录十来个日程一年下来几千条记录每条记录序列化成 JSON 字符串之后体积也不大Preferences 完全扛得住。使用过程很简单全局初始化一个 Preferences 实例然后通过 put 和 get 读写值。需要注意的点是Preferences 的写操作默认是异步落盘的调用 flush 强制刷新才能保证数据不丢。我在每次修改日程后都会在页面离开或数据变更的时机调用 flush实测下来非常稳。真正需要思考的是什么时候该切换到关系型数据库。如果以后要支持多用户、复杂条件查询、跨表关联或者数据量达到数万级那就得上 SQLite 了。鸿蒙提供了关系型数据库接口建表、增删改查和传统 SQLite 几乎一样。但现阶段宝贝日程表用不到所以我只在代码里留了一个 DataRepository 接口后续如果换 SQLite页面层完全不用动。import { preferences } from kit.ArkData; const PREF_NAME baby_schedule_store; const KEY_SUFFIX _schedules; export async function loadSchedules(date: string): PromiseScheduleItem[] { const pref await preferences.getPreferences(globalThis.context, PREF_NAME); const raw pref.getSync(${date}${KEY_SUFFIX}, []) as string; try { return JSON.parse(raw) as ScheduleItem[]; } catch (e) { return []; } } export async function saveSchedules(date: string, list: ScheduleItem[]): Promisevoid { const pref await preferences.getPreferences(globalThis.context, PREF_NAME); await pref.putSync(${date}${KEY_SUFFIX}, JSON.stringify(list)); await pref.flush(); }3.4 日历视图与日程列表不依赖三方库自己造轮子日历组件是宝贝日程表的门面但鸿蒙生态里的第三方日历控件还不太成熟直接引库风险不小要么 API 版本对不上要么样式定制起来非常痛苦。我最终选择用 Grid 组件自己写一个单月日历。核心思路是计算某个月的第一天是星期几、这个月有多少天然后生成一个 6 行 7 列的二维数组把日期数字填进 GridItem。选中日期的逻辑用一个 State selectedDate 管理点击格子就把选中状态刷过去下方日程列表则根据 selectedDate 去加载当天数据。跨月切换我用上一月、下一月两个按钮每次切换时重新计算月份数组。这个实现虽然朴素但没有任何不可控的依赖样式上我用圆角背景高亮今天和选中日视觉上也足够清爽。日期计算里有几个容易忽视的边界情况2 月 29 日、大月 31 日、小月 30 日、某月第一天正好是周日时要不要补满前一行。这些坑我在写的时候都遇到过解决方案是统一用一个 getMonthMatrix(year, month) 工具函数生成网格内部用 Date 对象去推算算完再转成纯数据避免在 UI 层做日期运算。日程列表部分我用 ForEach 渲染当天日程每个 item 展示了标题、时间、标签和完成状态。这里有个优化点数据量大的列表建议用 LazyForEach它会按需创建组件滚动时才渲染当前视口内的项。宝贝日程表单日数据撑死了不超过十个所以用 ForEach 就够了但如果以后做月视图聚合一次性渲染几百条的话一定要换成 LazyForEach。4. 几个磨人的细节多选删除、重复提醒与空状态4.1 ArkTS 多选列表与批量删除的实现过程多选删除这个功能看着简单但 ArkTS 里稍不留神就会踩响应式更新的坑。我的交互设计是长按某个日程项进入编辑模式此时每个 item 左侧出现复选框顶部出现“全选”和“删除”按钮点击删除后批量移除选中的日程。退出编辑模式可以通过点空白区域或点击完成按钮。数据层面我用两个状态字段控制isEditMode 和 selectedIds。isEditMode 是布尔值控制整页的选中态 UI 显示selectedIds 我一开始用 Set 存储选中项 id。用起来发现一个问题ArkUI 对 Set 内部变化的感知不是很直接调用 add 和 delete 之后 UI 可能不会立刻更新导致复选框勾选状态有时候不刷新。我把 selectedIds 换成了普通数组每次勾选时用数组展开生成新数组先判断当前 id 在不在数组里在就 filter 掉不在就展开旧数组加进去。这样一来每次都是给状态字段赋一个新数组ArkUI 的响应式系统能稳定感知变化复选框就再也没出现过勾选不上的问题。批量删除时同样基于这个数组过滤原列表注意删除后要把 selectedIds 清空否则下次进入编辑模式会残留之前选中的状态。function toggleSelect(id: string) { if (this.selectedIds.includes(id)) { this.selectedIds this.selectedIds.filter(item item ! id); } else { this.selectedIds [...this.selectedIds, id]; } } function deleteSelected() { this.schedules this.schedules.filter(item !this.selectedIds.includes(item.id)); this.selectedIds []; }4.2 重复日程与本地通知提醒重复日程是日程管理应用的标配但实现起来远比看起来复杂。我的方案是存储层只保存一条“母日程”里面通过 repeatRule 字段标明重复规则在查询某天的日程时把当天的普通日程取出来再扫描所有重复日程逐个判断它是否覆盖当天如果覆盖就生成一个临时的“日程实例”合并到返回列表里。这样设计的好处是绝无冗余数据改一条母日程能自动同步到所有日期坏处是查询时要多一次遍历和日期运算。判断规则很简单每天重复的直接返回每周重复的对比星期几是否一致每月重复的对比几号是否一致。注意月末边界比如 1 月 31 日的每月日程在 2 月遇到没有 31 日的情况时直接跳过这点逻辑要写清楚不然用户在月底某天会突然发现日程莫名消失。通知提醒这边我用的是鸿蒙的通知能力和提醒代理能力。首先需要请求通知权限这部分要在 module.json5 里声明对应权限并在运行时调用通知管理接口请求授权。然后添加提醒时通过提醒代理创建一个定时提醒到时间后会弹出本地通知。日程被删除或编辑后要对应取消或更新提醒否则会出现“日程删了通知还在”的尴尬。这个环节我在测试机上遇到了提醒时间偏差的问题查了半天发现是模拟器对后台定时任务限制比较严格换成真机就正常了所以大家开发提醒功能时最好以真机验证为准。4.3 空状态与边界情况处理一个完整的日程表应用空状态设计得好不好直接影响第一印象。我做了三种空状态当天没有任何日程时显示一个“今天还没有安排”的插画加引导按钮搜索不到结果时显示“换个关键词试试”多选删除后列表为空时自动退出编辑模式并给一个轻量提示。这些在代码里就是几个条件判断但能把粗糙的 Demo 变成有产品感的 App。边界情况还包括日期切换时数据和选中状态要同步重置我是在日期点击事件里统一处理同时把滚动位置归零避免用户看到上一天的残留内容跨天时如果 App 没被打开当天数据要正常显示这依赖前面提到的纯字符串日期方案逻辑上不会因为时区变化导致查错日期另外编辑一条已完成的日程时完成后字段要保持原样不能因为编辑操作被重置。5. 调试、测试与性能排查实录5.1 hdc 常用命令速查如果用 Studio 开发真机调试、查看日志、安装应用这些操作都是点按钮完成的但 CLI 模式下全靠 hdc 命令。hdc 是 HarmonyOS 的命令行工具和 Android 的 adb 类似。日常用最多的是这几个hdc list targets 查看当前连接的设备多设备时加 -t 参数指定设备hdc install xxx.hap 直接安装应用到真机hdc shell 进入设备终端hdc file send 把文件推到设备指定目录。这几个命令能覆盖 90% 的日常调试需求。日志这块我用 hdc hilog加上关键字过滤来定位问题。比如只输出我自己打的日志可以这样操作先用 hilog 清空缓存然后复现问题再用 grep 过滤项目名。复杂一点的崩溃信息hilog 里会带有堆栈信息定位到具体的 ArkTS 文件和方法排查效率比瞎猜高得多。5.2 模拟器与真机我都怎么验证开发前期我用模拟器验证 UI 和基本交互。模拟器的优势是启动快、截图方便适合快速看布局效果劣势是无法完整模拟通知提醒、后台任务这类依赖真实系统的能力而且 CPU 指令集和真机有差异某些性能边界场景测不出来。进入提醒功能开发阶段后我基本以真机为准。真机调试前需要先在开发者选项里打开 USB 调试然后在 hdc list targets 里看到设备编号。第一次连接会要求信任调试电脑手机上确认一下就行。这里有一个非常容易踩的坑真机的 HarmonyOS 版本和工程 targetSdkVersion 不匹配时安装包可能能装上但运行到某些新 API 直接崩溃或者功能静默失效所以上线前务必用主流版本测试不要只在自己手上一台机器测完就提审。5.3 日志排查与性能优化心得日常开发中我习惯在关键流程里加 hilog 日志比如页面加载、数据读取完成、保存成功这些节点。这样后期如果用户反馈异常可以从日志里快速还原操作路径。日志的级别也要用好info 用于业务节点warn 用于异常但不影响流程的情况error 用于真正会出错的逻辑别所有日志一骨碌打成 info不然排错时间直接翻倍。性能上宝贝日程表这个体量的 App 本不需要太多优化但日历翻页和列表滚动如果处理不好还是会有割裂感。我发现一个容易忽略的性能杀手是 ForEach 的 key 生成函数。如果 key 生成不当列表数据变化时会重建大量组件。我统一用日程 id 作为 key配合 LazyForEach 之后列表滑动流畅度提升非常明显。另外减少不必要的状态更新也很重要比如只在滚动停止时刷新当前时间指示条而不是每秒触发一次CPU 占用直接降下来。6. 从构建到上架hap 打包、签名与 AGC 发布6.1 用 hvigor 命令构建 hap 产物打包是 CLI 流程里最爽的一环。在工程根目录执行 hvigorw assembleHap构建就开始了。第一次构建会慢一些因为要下载依赖和编译后面的增量构建通常几秒到几十秒就能完成比 Studio 里点按钮等待的体验更可控。构建完成后HAP 产物在 entry/build/default/outputs/default 目录下文件名字一般是 entry-default-signed.hap 或 entry-default-unsigned.hap。区分是否签名很简单看名字里有没有 signed。Debug 构建默认使用自动签名配置可以直接装到真机Release 构建用于上架需要配好正式的签名材料。命令行构建的好处在这里体现得特别明显我写了一个 release.sh 脚本按顺序执行版本号替换、依赖安装、单元测试、hvigor 构建、产出物复制整个过程十分钟内完成每次发布都保证是同一个流程不会出现“在别人电脑上打包了一次就打不出来了”的问题。6.2 签名配置从自动签名到手动签名签名是上架前最容易出错的环节没有之一。开发阶段我用了自动签名在 AGC 后台创建一个应用然后在本地配置好 AGC 的登录凭证工程同步时自动生成调试证书和 Profile真机调试和本地安装都非常方便。但上架时必须用正式证书这就要换成手动签名。手动签名的步骤比较固定先生成密钥库文件.p12然后基于这个密钥库生成证书签名请求.csr把 CSR 上传到 AGC 生成证书.cer再为应用创建 Profile.p7b最后在 build-profile.json5 里把这些文件路径和密码填好。我有一次构建成功后安装却提示“签名与包名不匹配”最后发现是 Profile 里配置的包名和应用的 bundleName 大小写不一致这种错误极其隐蔽检查签名信息时一定要逐字符比对。签名相关的文件一定要进 git 忽略列表尤其是 .p12 这种私钥文件泄露了别人就能用你的证书签名造成上架安全事故。我在项目里加了 .gitignore 规则把签名目录整个排除掉只保留一份签名配置模板。6.3 AGC 上架完整流程与审核避坑上架操作在 AppGallery Connect 后台进行。第一步是创建应用包名必须和工程里的 bundleName 一致。然后要完善应用信息名称、图标、简介、截图、分类。截图分辨率不合规是审核最常见的驳回原因建议直接到后台下载官方截图模板用模板生成之后再做上传省得来回折腾。版本管理页面里上传前面构建出的 Release hap填写版本号和更新说明然后提交审核。提交之前有几点一定要自查隐私声明是否完整填写涉及通知权限的应用需要在隐私弹窗里明确说明用途是否存在测试代码和调试日志我的 release 构建脚本会自动关闭日志开关从机制上杜绝残留各个分辨率的截图是否和实际界面一致不要拿开发阶段的占位图上传。审核周期方面不同时期不一样我这次大概等了两三天。第一次提交因为隐私声明里没有明确写清楚“通知权限用于日程提醒”被驳回过一次修改后重新提交就通过了。通过之后应用会在几分钟内完成上架用户就能在应用市场搜到了。后续每次版本更新都是同样的流程只是审核会快一些因为应用已经有了上架记录。7. 常见问题速查与避坑技巧问题现象根本原因解决办法hvigorw 命令找不到Node 全局目录不在 PATH把 npm 全局 bin 目录加入 PATH或用 npx 执行构建报 API version mismatchCLI 与 SDK 版本不匹配使用对应版本的 CLI升级 SDK 到匹配版本真机安装提示签名错误证书或 Profile 与包名不匹配重新生成签名材料逐字符核对 bundleNameForEach 列表更新不刷新修改了数组项内部字段但未触发响应式用 Observed/ObjectLink 或整体重新赋值日程删除了但通知还会响删除日程时未取消提醒删除接口里同时调用提醒代理取消任务上架审核因隐私被驳回隐私声明未包含权限用途说明在隐私弹窗中明确说明通知权限使用场景真机运行时某功能失效目标 API 版本和系统版本不匹配在主流系统版本机型上多测几轮关于 hdc 连接不上的问题我也多说一句如果你是初次接触 CLI 开发最容易卡在第一步“设备不显示”。90% 的情况是手机没有开启 USB 调试或者电脑缺少对应驱动先别怀疑 hdc 本身。Windows 用户注意在设备管理器里看是否有未知设备macOS 用户检查系统设置里是否允许终端访问可移动设备。这些都排除了再去翻日志不然纯浪费半天时间。另外如果你以后有心去参加鸿蒙开发岗位的面试这个项目其实是个不错的面试作品。面试官问鸿蒙状态管理你可以讲讲 State、Prop、Link、Observed 各自的适用边界问工程结构你可以说说 HAP、HAR、HSP 的差异和选型原因问打包上架你可以把签名、AGC 流程讲得清清楚楚。这些都是在真实开发中沉淀下来的比背一百道面试题更有说服力。最后再分享一个小建议用 DevEco CLI 做开发的过程中我非常建议大家把所有能自动化的环节都写成脚本固化下来。版本号替换、签名、打包、上传这些重复劳动每多做一次就是在浪费生命。踩过几次坑之后我现在的发布流程就是一条命令构建、测试、打包、确认产物一气呵成。手动的步骤越少人为犯错的空间就越小上架这件事自然也就变成一个不那么让人紧张的操作了。