用 ArkTS + ArkUI 搭一个可上架的鸿蒙工程骨架 这是《鸿蒙开口练APP实战手记》的第一篇。这个系列不写Hello World 复读所有内容都来自一个真实项目一款 AI 口语表达教练 AppHarmonyOS 平台ArkTS ArkUI从空白工程一路做到上架。开篇先解决最基础、也最容易被糊弄过去的问题——工程骨架。很多人的第一个鸿蒙工程是 DevEco Studio 模板生成的能跑就收工。等到要上架才发现签名没配、版本号没规划、权限声明乱塞、构建只会点按钮。这篇文章把可上架倒推到第一天逐项讲清楚。1. 先定 SDK 基线compile 24 / target 24 / compatible 23鸿蒙工程的 SDK 有三个数字对应build-profile.json5里的字段{ products: [ { name: default, signingConfig: default, targetSdkVersion: 6.1.1(24), compatibleSdkVersion: 6.1.0(23), runtimeOS: HarmonyOS } ] }compileSdkVersion用什么 SDK 编译决定你能调用哪些 API。在 DevEco 里随 SDK 安装确定选最新稳定版即可。targetSdkVersion声明我为这个版本做过完整适配系统按这个版本的行为对待你的应用。不要填一个你没真机验证过的版本。compatibleSdkVersion最低兼容版本。填 23 意味着 API 23 的设备也能装代价是你用到 API 24 独有能力时必须自己做分支判断。我们的选择是24/24/23理由很朴素target 跟上最新稳定版拿到完整行为compatible 下探一级多覆盖一批存量设备同时把API 23 真机验证写进验收清单防止基线只是纸面数字。这个决定应该在项目第一张 ADR架构决策记录里冻结因为它影响后面每一个系统能力的选型——比如我们后面选 CoreSpeechKit 做离线语音识别就是先确认了它在 API 23 上行为完整。2. 工程全景三个配置文件各管一件事一个标准鸿蒙工程的根目录长这样SpeakLab/ ├── AppScope/ │ └── app.json5 # 应用级元数据全局唯一一份 ├── entry/ # 主模块entry 类型装入口 │ └── src/main/ │ ├── module.json5 # 模块级声明Ability、权限、页面 │ ├── ets/ # ArkTS 源码 │ └── resources/ # 资源字符串、颜色、媒体、rawfile ├── build-profile.json5 # 构建配置签名、产物、构建模式 ├── oh-package.json5 # 依赖声明 └── hvigorfile.ts # 构建脚本入口新手最容易混淆的是前三个文件的分工一句话记法文件管什么类比AppScope/app.json5这个应用是谁bundleName、vendor、版本号、图标、名称Android 的 applicationId versionCodeentry/src/main/module.json5这个模块有什么Ability、页面路由、权限声明AndroidManifest.xmlbuild-profile.json5怎么构建签名、SDK 版本、buildModebuild.gradleapp.json5上架信息的第一现场{ app: { bundleName: com.xiangshikeji.speaklab, vendor: xiangshikeji, versionCode: 1, versionName: 1.0.0, icon: $media:layered_image, label: $string:app_name } }三个纪律都是从上架返工里学来的bundleName 一次定终身。上架后不可改用反域名且和主体一致别用com.example.xxx起步。versionCode 是上架的单调轴。首发定 1之后每次提交至少 1versionName 给人看versionCode 给市场看。名称和图标走资源引用$string:/$media:不要硬编码。用户可见名称收敛在一个字符串资源里后续改名字、做多语言都只动一处。module.json5权限和 Ability 的声明处{ module: { name: entry, type: entry, mainElement: EntryAbility, deviceTypes: [phone], abilities: [ { name: EntryAbility, srcEntry: ./ets/entryability/EntryAbility.ets, startWindowBackground: $color:start_window_background, exported: true, skills: [ { entities: [entity.system.home], actions: [ohos.want.action.home] } ] } ], requestPermissions: [ { name: ohos.permission.MICROPHONE, reason: $string:sl_mic_reason, usedScene: { abilities: [EntryAbility], when: inuse } }, { name: ohos.permission.INTERNET } ] } }两个要点user_grant 权限必须给 reason且 reason 走资源。ohos.permission.MICROPHONE这类用户授权权限审核会看你声明的理由文本。reason引用字符串资源而不是写死方便后续按审核意见调整文案。权限声明是最小集纪律的起点。工程第一天就要忍住先都加上再说的冲动——每多一个权限上架审核就多一份解释成本。我们只有麦克风业务必需和 INTERNETAI 功能必需两个。build-profile.json5签名与严格模式{ app: { signingConfigs: [ { name: default, type: HarmonyOS, material: { certpath: /Users/xxx/.ohos/config/default_xxx.cer, keyAlias: debugKey, profile: /Users/xxx/.ohos/config/default_xxx.p7b, signAlg: SHA256withECDSA, storeFile: /Users/xxx/.ohos/config/default_xxx.p12 } } ], products: [ { name: default, signingConfig: default, buildOption: { strictMode: { caseSensitiveCheck: true, useNormalizedOHMUrl: true } } } ] } }签名从第一天就配好。DevEco 的自动化签名会在~/.ohos/config/生成调试证书并写回这个文件。哪怕只是真机调试鸿蒙也要求签名 HAP——先把这条链路跑通免得能编译不能装机卡住节奏。注意keyPassword/storePassword是 DevEco 托管的密文这个文件不要提交到公开仓库。strictMode 两个开关建议开。caseSensitiveCheck强制 import 路径大小写敏感——macOS 文件系统默认不敏感不开这个代码在 CI 或同事机器上会以莫名其妙的方式挂掉useNormalizedOHMUrl统一模块 URL 规范避免依赖解析的隐性分叉。3. 源码目录第一天就分层entry/src/main/ets/下的目录结构决定了三个月后这个工程还能不能维护。我们的约定ets/ ├── entryability/ # EntryAbility唯一入口 Ability ├── pages/ # 页面 Destinationhome / settings / report / history… ├── sheets/ # 全局弹层权限说明、统计、教练历史 ├── common/ │ ├── components/ # 可复用 UI 组件 │ ├── theme/ # 语义化主题 token │ ├── navigation/ # 路由与壳层 │ ├── types/ # 领域类型 │ ├── store/ # 状态管理 │ ├── lexicon/ # 领域服务词库 │ ├── asr/ # 系统能力 port语音识别 │ ├── ai/ # 系统能力 portAI 调用 │ └── settings/ # 持久化 port设置 └── spike/ # 技术验证代码与正式代码物理隔离核心规则只有两条页面不直接碰系统 Kit。语音识别、AI 网络调用、权限申请全部收口到common/下的 port 层页面只依赖自己的 service 接口。这条规则让换实现和写假数据都变成只动一处的事。命名前缀统一。类/服务统一SpeakLab前缀资源统一sl_前缀如$string:sl_mic_reason日志 tag 统一SpeakLab。前缀看起来是小事但当你要在 400 多条测试日志或整包字符串资源里grep 时它就是救命绳。4. 命令行构建别只会点按钮DevEco 的 Run 按钮很方便但可复现的构建必须能在命令行完成——这是后续做 CI、做验收、做干净重建的前提。两个环境变量是关键export DEVECO_SDK_HOME/Applications/DevEco-Studio.app/Contents/sdk export PATH/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin:\ /Applications/DevEco-Studio.app/Contents/tools/node/bin:$PATH注意DEVECO_SDK_HOME指向Contents/sdk这一层不要指到里面的default/子目录——这是新手最常见的踩坑点指错了 hvigor 会报找不到 SDK 组件。然后一条命令打出签名包hvigorw --mode module \ -p productdefault \ -p moduleentrydefault \ -p buildModerelease \ assembleHap --no-daemon产物在entry/build/default/outputs/default/entry-default-signed.hap。工程里把它包一层脚本scripts/build-entry-hap.sh构建前先hvigorw clean、构建后打印 HAP 路径、大小和 SHA-256scripts/build-entry-hap.sh release # build-entry-hap: release HAP ready # build-entry-hap: path.../entry-default-signed.hap # build-entry-hap: sizexxM sha256f09f7a65...为什么要打印 SHA-256因为验收构建和演示构建必须是同一个东西。Hash 一贴谁都没有歧义。这个小习惯在后面做独立验收时救过我们很多次。装到真机hdc install -r entry/build/default/outputs/default/entry-default-signed.hap5. 小结骨架的检查清单到这里一个朝着上架去的鸿蒙工程骨架就齐了。按清单自查SDK 基线compile/target/compatible写进 ADR最低版本有真机验证计划app.json5bundleName 定终身、versionCode 从 1 起、名称图标走资源引用module.json5权限最小集user_grant 权限的 reason 走字符串资源build-profile.json5签名链路跑通strictMode 两个开关打开ets/分层pages / sheets / common 各司其职系统能力收口 port 层命名前缀统一SpeakLab/sl_命令行能 clean 构建出签名 HAP且打印 SHA-256