用 ArkTS + ArkUI 搭一个可上架的鸿蒙工程骨架

这是《鸿蒙开口练APP实战手记》的第一篇。这个系列不写"Hello World 复读",所有内容都来自一个真实项目:一款 AI 口语表达教练 App,HarmonyOS 平台,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 # 构建脚本入口
新手最容易混淆的是前三个文件的分工,一句话记法:
|
文件 |
管什么 |
类比 |
|---|---|---|
|
|
这个应用是谁:bundleName、vendor、版本号、图标、名称 |
Android 的 applicationId + versionCode |
|
|
这个模块有什么:Ability、页面路由、权限声明 |
AndroidManifest.xml |
|
|
怎么构建:签名、SDK 版本、buildMode |
build.gradle |
app.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,之后每次提交至少 +1;versionName 给人看,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引用字符串资源而不是写死,方便后续按审核意见调整文案。 -
权限声明是"最小集"纪律的起点。工程第一天就要忍住"先都加上再说"的冲动——每多一个权限,上架审核就多一份解释成本。我们只有麦克风(业务必需)和 INTERNET(AI 功能必需)两个。
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/ # 页面 Destination(home / settings / report / history…)
├── sheets/ # 全局弹层(权限说明、统计、教练历史)
├── common/
│ ├── components/ # 可复用 UI 组件
│ ├── theme/ # 语义化主题 token
│ ├── navigation/ # 路由与壳层
│ ├── types/ # 领域类型
│ ├── store/ # 状态管理
│ ├── lexicon/ # 领域服务:词库
│ ├── asr/ # 系统能力 port:语音识别
│ ├── ai/ # 系统能力 port:AI 调用
│ └── 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 product=default \
-p module=entry@default \
-p "buildMode=release" \
assembleHap --no-daemon
产物在 entry/build/default/outputs/default/entry-default-signed.hap。工程里把它包一层脚本(scripts/build-entry-hap.sh),构建前先 hvigorw clean、构建后打印 HAP 路径、大小和 SHA-256:
scripts/build-entry-hap.sh release
# build-entry-hap: release HAP ready
# build-entry-hap: path=.../entry-default-signed.hap
# build-entry-hap: size=xxM sha256=f09f7a65...
为什么要打印 SHA-256?因为"验收构建"和"演示构建"必须是同一个东西。Hash 一贴,谁都没有歧义。这个小习惯在后面做独立验收时救过我们很多次。
装到真机:
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
5. 小结:骨架的检查清单
到这里,一个"朝着上架去"的鸿蒙工程骨架就齐了。按清单自查:
-
SDK 基线(compile/target/compatible)写进 ADR,最低版本有真机验证计划
-
app.json5:bundleName 定终身、versionCode 从 1 起、名称图标走资源引用 -
module.json5:权限最小集,user_grant 权限的 reason 走字符串资源 -
build-profile.json5:签名链路跑通,strictMode 两个开关打开 -
ets/分层:pages / sheets / common 各司其职,系统能力收口 port 层 -
命名前缀统一(
SpeakLab/sl_) -
命令行能 clean 构建出签名 HAP,且打印 SHA-256
更多推荐




所有评论(0)