鸿蒙应用上架与签名打包

本文梳理 HarmonyOS NEXT 应用上架的完整链路:密钥库、证书请求、数字证书与 Profile 四件套的作用与生成顺序,调试签名与发布签名的差异,AGC 侧的应用创建与调试设备注册,hvigor 打包命令与产物路径,版本号管理规则与审核驳回原因,并汇总 bundleName 不一致、versionCode 未递增等高频坑。

开篇:签名是上架的第一道门

鸿蒙应用的安装与分发完全建立在签名之上:系统只安装签名合法且 Profile 授权的应用,应用市场只接受用发布证书签名的包。很多开发者在功能开发完成后卡在最后一步,反复被"签名校验失败"“应用未授权"这类错误拦住,根本原因是没搞清签名链路上四个文件的职责。

本文按"准备证书、配置签名、打包产物、提交审核"的顺序把这条链路走一遍。如果你还没建好工程,建议先读 HarmonyOS NEXT 全景与开发环境搭建 。

一、签名体系四件套

鸿蒙的签名体系由四个文件构成,它们存在明确的生成依赖关系,顺序不能颠倒。

文件扩展名作用生成方
密钥库.p12保存私钥,签名时使用开发者本地生成
证书请求.csr承载公钥与主体信息由密钥库导出
数字证书.cer由 CA 签发,证明公钥归属AppGallery Connect 签发
Profile.p7b描述应用权限与设备白名单AppGallery Connect 生成

生成顺序是:先在本地用密钥库工具生成 .p12 与 .csr,把 .csr 上传到 AGC 换取 .cer,再在 AGC 上基于该证书创建 .p7b。四者缺一不可,且必须互相对应:用 A 证书签名的包配 B 证书生成的 Profile,一定会校验失败。

.p7b 是唯一与"这台设备能不能装"直接相关的文件。调试用的 Profile 里写死了允许安装的设备 UDID 列表,漏加设备就会导致安装时报"应用未授权”。

1.1 证书链与签名算法

签名算法在配置里写成 SHA256withECDSA,即基于椭圆曲线的 ECDSA 签名配合 SHA256 摘要。系统在安装校验时会沿着"包签名到数字证书到根证书"的链条逐级验证,任何一环断裂都会失败。

这解释了一个常见现象:只替换 .cer 而不替换 .p7b,安装必然失败。因为 .p7b 内部记录了签发它的证书信息,证书换了,Profile 里记录的指纹就对不上了。

1.2 密钥库不是备份文件

.p12 密钥库一旦丢失,对应的证书就无法再用于签名,线上应用将无法发布新版本。因此密钥库与密码必须纳入团队的密码管理流程,而不是只存在某个人的笔记本里。这一点和任何代码签名体系的要求一致。

二、调试签名与发布签名

两种签名的用途完全不同,混用是常见错误。

维度调试签名发布签名
用途真机调试提交应用市场
Profile 类型调试 Profile发布 Profile
设备限制仅白名单设备无限制
证书类型调试证书发布证书
有效期通常较短较长
能否上架否是

调试证书与发布证书在 AGC 上是两个独立的入口,不能互相替代。用调试证书打出的包上传到 AGC 会被直接拒绝。

2.1 为什么需要两套证书

根因在于设备授权模型。调试 Profile 里包含允许安装的设备 UDID 白名单,这个白名单只对开发调试有意义;发布 Profile 不限制设备,但它要求证书是发布证书。两套 Profile 的结构不同,系统据此区分包的用途。

工程上的建议是把两套签名配置同时写进 build-profile.json5,通过 products 切换,避免每次打包时手改配置。

{
  "app": {
    "signingConfigs": [
      { "name": "debug", "type": "HarmonyOS", "material": { "profile": "./signature/debug.p7b" } },
      { "name": "release", "type": "HarmonyOS", "material": { "profile": "./signature/release.p7b" } }
    ],
    "products": [
      { "name": "default", "signingConfig": "debug" },
      { "name": "release", "signingConfig": "release" }
    ]
  }
}

注意上面为简洁省略了 material 中的其余字段,实际配置必须补全 certpath、storeFile、keyAlias、storePassword、keyPassword、signAlg。

三、AppGallery Connect 侧准备

3.1 实名认证与创建应用

AGC 的第一步是完成开发者实名认证,个人与企业认证所需材料不同。认证通过后才能创建项目与应用。

创建应用时最关键的一个字段是包名(bundleName),它必须与工程 app.json5 中的 bundleName 完全一致,且全局唯一。一旦应用创建成功,包名不可修改。因此建议在动手写代码前就把包名定好,格式通常是反向域名,例如 com.example.todoapp。

3.2 注册调试设备

真机调试前必须把设备 UDID 加到 AGC 的设备列表里。

获取 UDID 的方式有两种:一是在 DevEco Studio 的设备管理界面直接复制,二是通过 hdc 命令读取。

hdc list targets             # 列出已连接设备
hdc shell bm get --udid      # 获取指定设备的 UDID

拿到 UDID 后在 AGC 的"设备管理"里添加,再重新生成或更新调试 Profile,把设备包含进去。改完 Profile 必须重新下载并替换工程里的 .p7b 文件,否则本地的 Profile 仍是旧的。

3.3 项目与应用的层级关系

AGC 的组织模型是"项目包含应用":一个项目下可以挂多个应用,证书与 Profile 都创建在应用层级。团队协作时最常见的混乱是把证书创建在了另一个项目下,导致 Profile 无法关联到目标应用。

判断方法很简单:在 AGC 的应用详情页能看到的证书列表,才是该应用可用的证书。跨项目复用证书在鸿蒙的签名体系里是不成立的。

四、DevEco Studio 中配置签名

4.1 自动签名

DevEco Studio 提供一键自动签名:在 File 菜单进入 Project Structure,选择 Signing Configs,勾选自动签名即可。IDE 会自动完成密钥库生成、证书申请与 Profile 下载的全过程。

自动签名的优点是零门槛,缺点是生成的证书与 Profile 绑定当前账号与当前设备,换机器或换账号时需要重新生成,不适合团队协作。

4.2 手动签名

团队协作推荐手动签名:由一个人统一生成 .p12 与 .csr,在 AGC 换取 .cer 与 .p7b,再把三个文件(.p12、.cer、.p7b)连同密码分发给团队成员,各自在本地配置。

这样做的另一个好处是发布包与调试包可以共用同一套证书,避免"本地能跑、打包上架失败"的割裂。

4.3 build-profile.json5 配置片段

签名信息写在工程根目录的 build-profile.json5 中。

{
  "app": {
    "signingConfigs": [
      {
        "name": "release",
        "type": "HarmonyOS",
        "material": {
          "certpath": "./signature/release.cer",
          "storePassword": "0000001B0A2C3D4E5F",
          "keyAlias": "releaseKey",
          "keyPassword": "0000001B0A2C3D4E5F",
          "profile": "./signature/release.p7b",
          "signAlg": "SHA256withECDSA",
          "storeFile": "./signature/release.p12"
        }
      }
    ],
    "products": [
      {
        "name": "default",
        "signingConfig": "release",
        "compatibleSdkVersion": "5.0.0(12)",
        "runtimeOS": "HarmonyOS"
      }
    ]
  }
}

三个密码字段(storePassword、keyPassword)在 IDE 里会以加密串形式保存,手动填写时若直接写明文,IDE 会在下次打开时重新加密,容易造成配置被覆盖。建议始终通过 IDE 的签名界面填写,而不是手改文件。

五、打包命令与产物

5.1 hvigor 命令

鸿蒙的构建工具是 hvigor,命令行入口是工程根目录下的 hvigorw。

./hvigorw assembleHap --mode module -p product=default                     # 构建调试 HAP
./hvigorw assembleApp --mode project -p product=default -p buildMode=release  # 构建发布 APP 包
./hvigorw clean                                                            # 清理构建产物

buildMode=release 会开启代码混淆与资源压缩,这也是发布包体积明显小于调试包的原因。CI 环境中通常需要配合 --no-daemon 关闭常驻进程。

5.2 产物路径

产物路径用途
调试 HAPentry/build/default/outputs/default/entry-default-unsigned.hap本地安装验证
签名 HAPentry/build/default/outputs/default/entry-default-signed.hap真机调试
发布 APPbuild/outputs/default/*.app提交 AGC

上架时提交的是 .app 文件而不是 .hap。.app 是包含多个 HAP 与资源索引的聚合包,.hap 是单个模块的安装包。用错了文件类型,AGC 会提示格式不支持。

5.3 CI 中的自动打包

在持续集成环境中,打包命令需要加上 --no-daemon 避免守护进程驻留,签名材料则通过环境变量或密钥管理服务注入,绝不提交到代码仓库。

#!/bin/bash
set -euo pipefail

mkdir -p signature
echo "$SIGN_STORE_FILE" | base64 -d > signature/release.p12
echo "$SIGN_CERT" | base64 -d > signature/release.cer
echo "$SIGN_PROFILE" | base64 -d > signature/release.p7b

./hvigorw clean --no-daemon
./hvigorw assembleApp --mode project \
  -p product=release \
  -p buildMode=release \
  --no-daemon

ls -lh build/outputs/default/

脚本分三步:先把环境变量里的三份签名材料还原到本地目录,再清理并构建发布包,最后列出产物供后续上传。

CI 中另一个容易出问题的地方是 versionCode。建议由流水线根据构建序号自动写入,避免人工修改 app.json5 造成冲突。

5.4 打包报错速查

现象可能原因处理方式
提示签名校验失败证书与 Profile 不匹配用同一套材料重新生成 Profile
安装报应用未授权设备 UDID 不在白名单添加设备并更新 Profile
密钥库打开失败密码或别名错误核对 keyAlias 与两个密码
找不到签名配置products 引用的名字拼错检查 signingConfigs 与 products 的 name
构建卡住无输出hvigor 守护进程异常追加 –no-daemon 重试
上传提示格式不支持提交了 .hap 而非 .app改用 assembleApp 的产物

这张表覆盖了绝大多数"打包到一半失败"的场景。排查顺序建议是:先确认包名与 AGC 一致,再确认证书与 Profile 成对,最后确认构建命令与产物类型。

六、混淆与资源压缩

release 构建会启用混淆,但混淆强度需要显式配置。配置写在模块级 build-profile.json5 的 buildOption.arkOptions 中。

{
  "buildOption": {
    "arkOptions": {
      "obfuscation": {
        "ruleOptions": {
          "enable": true,
          "files": ["./obfuscation-rules.txt"]
        },
        "consumerFiles": ["./consumer-rules.txt"]
      }
    }
  }
}

obfuscation-rules.txt 里可以开启更激进的选项。

-enable-property-obfuscation
-enable-toplevel-obfuscation
-enable-filename-obfuscation
-enable-export-obfuscation
混淆选项作用主要风险
enable-property-obfuscation混淆属性名序列化与反射失效
enable-toplevel-obfuscation混淆顶层名动态引用失效
enable-filename-obfuscation混淆文件名动态 import 失效
enable-export-obfuscation混淆导出名跨模块引用失效

开启属性混淆后,凡依赖属性名的场景都会出问题,必须用 -keep-property-name 把需要保留的名字列进白名单。更关键的是,混淆后崩溃栈里的符号会变成短名,必须把构建产物中的 sourcemap 上传到 AGC,后台才能把堆栈还原成可读的函数名。这一步经常被遗漏,导致线上崩溃只能看到一堆无意义的字母。

七、版本号管理

鸿蒙应用有两个版本字段,含义完全不同。

字段类型是否用户可见规则
versionCode整数否每次提交必须严格递增
versionName字符串是展示给用户,如 1.0.0

versionCode 在 app.json5 中配置。AGC 在接收新包时会校验它是否大于线上版本,不递增会被直接驳回,这是最容易被忽略的规则。建议在 CI 中用构建号自动生成 versionCode,避免人工遗忘。

上架前可以用一段代码自检包名与版本号是否与预期一致,避免打包后才发现 bundleName 写错。

import { bundleManager, common } from '@kit.AbilityKit';

export async function readBundleInfo(context: common.UIAbilityContext): Promise<string> {
  const flag = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
  const info: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(flag);
  return `${info.name} ${info.versionName} ${info.versionCode}`;
}

把这段逻辑挂在一个仅调试版本可见的入口里,打包前运行一次,就能确认包名、版本名与版本号三项都符合预期。相比在 AGC 提交后才被驳回,这个自检的成本几乎为零。

BUILD_NUMBER=${CI_PIPELINE_ID:-1}
python3 - <<PY
import json, re
path = 'AppScope/app.json5'
text = open(path, encoding='utf-8').read()
text = re.sub(r'"versionCode":\s*\d+', f'"versionCode": ${BUILD_NUMBER}', text)
open(path, 'w', encoding='utf-8').write(text)
PY

把版本号交给流水线还有一个隐性收益:每次构建的 versionCode 都与流水线记录一一对应,出问题时能立刻定位到是哪次构建产出的包。

八、上架流程

阶段主要动作产出
准备实名认证、创建应用、确认包名AGC 应用记录
打包发布证书签名、release 构建签名后的 .app
填写信息应用介绍、截图、图标、分类商品信息
合规材料隐私政策、软著、权限说明审核附件
提交审核上传包并提交审核任务
测试发布邀请测试或公开测试测试版本
正式发布审核通过后上架线上版本

隐私政策是审核的重点,必须明确列出应用收集了哪些数据、用途是什么、如何删除。缺失或与实际行为不符都会导致驳回。软著(软件著作权)对部分类目是硬性要求,建议提前准备,办理周期通常以周计。

8.1 上架材料清单

材料是否必需说明
应用图标必需需要多档尺寸
应用截图必需至少三张,覆盖主要界面
应用介绍必需有字数上限
隐私政策必需需提供可公开访问的链接
软件著作权视类目部分类目为硬性要求
权限使用说明必需逐条对应实际申请的权限
测试账号视情况需登录的应用必须提供

8.2 测试渠道与灰度发布

AGC 提供邀请测试与公开测试两种测试渠道。邀请测试需要手动添加测试账号,适合小范围验证;公开测试有名额上限,适合放量前的压力验证。

测试版本与正式版本的 versionCode 必须不同,且测试版本不能直接转为正式版本,需要以正式包重新提交审核。因此不要把测试渠道当作"跳过审核的捷径",它只是把验证提前了一步。

九、常见驳回原因

  • bundleName 与 AGC 应用记录不一致,签名校验失败。
  • versionCode 未递增或重复。
  • 隐私政策缺失,或未覆盖实际申请的权限。
  • 使用了需要资质但未提供证明的类目。
  • 应用内存在未声明的第三方 SDK 数据收集行为。
  • 启动即崩溃、白屏或长时间无响应。
  • 截图与实际界面不符。
  • 发布包中残留调试日志或测试入口。
  • 软著或商标材料缺失。
  • 元服务包体积超出限制。
  • 测试渠道的包与正式包用了同一个 versionCode,提交正式版时被判定为重复。
  • 隐私政策链接不可公开访问,审核方打不开。

十、常见坑清单

  • 用调试证书打发布包,AGC 直接拒绝。
  • Profile 与证书不匹配,安装时报"应用未授权"。
  • 添加了调试设备 UDID 但没重新下载 Profile。
  • 密码或密钥别名填错,签名时提示密钥库打开失败。
  • 手改 build-profile.json5 的密码字段,被 IDE 重新加密后失效。
  • signingConfigs 的 name 与 products 中引用的名字不一致。
  • 提交 .hap 而非 .app。
  • release 构建未开混淆,包体积超标。
  • CI 中未关闭 hvigor 守护进程,导致构建卡住。
  • 换机器后仍用旧密钥库,签名结果与线上包不一致,无法覆盖安装。

签名与包体积、启动性能往往同时出问题,因为发布构建会开启混淆与压缩,可能暴露出调试构建中隐藏的缺陷。上线前建议用发布包完整跑一遍性能检查,方法见 鸿蒙原生应用性能优化与调试 。如果应用包含登录能力,账号体系与隐私政策的对应关系还需要额外核对,可参考 小程序登录与鉴权 里对授权边界的讨论。

小结

上架的难点几乎全部集中在签名链路上:四个文件必须互相对应,调试与发布两套证书不能混用,包名一旦确定不可更改。工程上的三条底线是:包名提前定死并与 AGC 保持一致,versionCode 交给 CI 自动递增,团队协作统一用手动签名分发证书。把这三件事做对,剩下的就是按流程填材料、等审核。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「鸿蒙开发」更多文章

  1. 鸿蒙 ohpm 包管理与 Hypium 测试框架
  2. ArkUI 动画体系与手势交互
  3. 鸿蒙应用安全:权限模型与 HUKS 密钥管理