这是《鸿蒙开口练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            # 构建脚本入口

新手最容易混淆的是前三个文件的分工,一句话记法:

文件

管什么

类比

AppScope/app.json5

这个应用是谁:bundleName、vendor、版本号、图标、名称

Android 的 applicationId + versionCode

entry/src/main/module.json5

这个模块有什么:Ability、页面路由、权限声明

AndroidManifest.xml

build-profile.json5

怎么构建:签名、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"
  }
}

三个纪律,都是从上架返工里学来的:

  1. bundleName 一次定终身。上架后不可改,用反域名且和主体一致,别用 com.example.xxx 起步。

  2. versionCode 是上架的单调轴。首发定 1,之后每次提交至少 +1;versionName 给人看,versionCode 给市场看。

  3. 名称和图标走资源引用$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/               # 技术验证代码(与正式代码物理隔离)

核心规则只有两条:

  1. 页面不直接碰系统 Kit。语音识别、AI 网络调用、权限申请,全部收口到 common/ 下的 port 层,页面只依赖自己的 service 接口。这条规则让"换实现"和"写假数据"都变成只动一处的事。

  2. 命名前缀统一。类/服务统一 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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐