SlideDrop 系列结束后,这一轮换到一个完全不同的工程阶段:应用写完了,准备上架 AppGallery Connect,怎么把“上传以后才发现的错误”提前挡在本地。

Demo 叫 ReleaseGuard。

它不是面向用户的业务 App,而是团队内部的发布预检工具。ReleaseGuard 会在生成正式 .app 包以后,把应用标识、版本、签名、目标 API、支持设备和构建产物做成一份可验证快照;手机端只是把这份快照和检查结果可视化,真正的构建信息来自发布流水线。

这个系列固定 6 篇:

01 包名、版本号与发布签名预检
02 权限声明、隐私标签与三方 SDK 合规映射
03 图标、截图、应用描述与内容分级素材校验
04 支持设备、权限 Profile 与包体有效性报告
05 版本升级、签名连续性与灰度发布前检查
06 CI 发布门禁、审核证据包与全链路验收

第一篇不碰隐私,也不碰运营素材,只解决上传包本身最基础的一层。

AppGallery Connect 当前的 HarmonyOS 包解析错误文档已经把常见问题写得很明确:bundleName 与 AppGallery Connect 不一致、使用了错误的 Profile、使用 debug Profile、证书过期、包未签名、包内设备类型与市场侧选择不匹配,都可能让上传直接失败;HarmonyOS 应用正式发布还需要 .app 包和正确的 release 签名链。

所以 01 的目标很克制:

在点“上传”以前,先让本地构建产物自己证明它是“我要发布的那个 App”。

本轮统一数据:

taskId:
release_20261002_01

bundleName local:
com.example.releaseguard

bundleName AGC:
com.example.releaseguard

versionName:
1.3.0

versionCode:
10300

buildMode:
release

signingConfig:
release_prod_202610

certificateState:
RELEASE_VALID

targetApi:
26

compatibleApi:
20

deviceTypes:
phone / tablet

artifact:
releaseguard_1.3.0.app

artifactSize:
18.4MB

checks:
8 / 8

risk:
0

status:
PACKAGE_READY

一、ReleaseGuard 不直接在手机里读取工程源码

最开始我也想过,让工具在运行时直接读取:

AppScope/app.json5
build-profile.json5
module.json5

但真正的 release 包里,这些“开发阶段文件”的存在形式、路径和构建产物都不应该被业务页面假设。

所以 ReleaseGuard 的第一层不是“运行时扫工程”,而是:

构建阶段
→ 生成 ReleaseManifestSnapshot.json
→ 打进 internal 构建
→ ReleaseGuard 页面读取快照

这样手机端看到的是本次 release 构建已经解析完成的数据,而不是重新猜一次工程配置。

快照模型:

export interface ReleaseManifestSnapshot {
  taskId: string

  bundleName: string
  agcBundleName: string

  versionName: string
  versionCode: number

  buildMode: 'debug' | 'release'

  signingConfig: string
  certificateState:
    'RELEASE_VALID' |
    'DEBUG_ONLY' |
    'EXPIRED' |
    'UNKNOWN'

  targetApi: number
  compatibleApi: number

  deviceTypes: string[]

  artifactName: string
  artifactSizeMb: number
}

这段模型解决的问题不是“怎么解析 json5”,而是给后续检查建立一个固定输入。

构建脚本、DevEco Studio 配置字段即使以后调整,ReleaseGuard 的检查器只要继续拿到同一份 Snapshot,就不用把业务判断全部改掉。

二、第一条门禁永远是 bundleName 一致

HarmonyOS Stage 模型里,应用级 bundleName 位于 AppScope/app.json5。AppGallery Connect 的包解析文档也明确指出,上传包中解析出的 bundleName 必须和 AppGallery Connect 创建应用时的包名一致;不一致会触发包解析失败。

这类问题很适合提前拦。

export interface CheckItem {
  key: string
  passed: boolean
  message: string
}

export class PackagePreflightRunner {
  checkBundleName(
    snapshot:
      ReleaseManifestSnapshot
  ): CheckItem {
    const passed =
      snapshot.bundleName ===
      snapshot.agcBundleName

    return {
      key:
        'BUNDLE_NAME',

      passed,

      message:
        passed
          ? 'bundleName match'
          : `${snapshot.bundleName} != ${snapshot.agcBundleName}`
    }
  }
}

当前:

local:
com.example.releaseguard

AGC:
com.example.releaseguard

结果为:

MATCH

这里不要用应用显示名称判断。

显示名称都叫 “ReleaseGuard”,包名仍然可能完全不同。

三、版本号不只要“有”,还要进入发布策略

当前版本:

versionName=1.3.0
versionCode=10300

ReleaseGuard 先做两层判断:

versionName 非空

versionCode
必须高于团队记录的上一正式发布值

例如:

export class VersionPreflight {
  check(
    snapshot:
      ReleaseManifestSnapshot,
    lastPublishedCode:
      number
  ): CheckItem[] {
    return [
      {
        key:
          'VERSION_NAME',

        passed:
          snapshot.versionName
            .trim()
            .length > 0,

        message:
          snapshot.versionName
      },

      {
        key:
          'VERSION_CODE',

        passed:
          snapshot.versionCode >
          lastPublishedCode,

        message:
          `${snapshot.versionCode} > ${lastPublishedCode}`
      }
    ]
  }
}

这一层不是在复制 AppGallery Connect 服务端校验。

它解决的是团队自己的发布纪律:

本地构建出来的版本
是不是比上一版新

如果版本号在本地就回退,就没必要等上传以后再发现。

四、正式包不能带 debug Profile 思维

当前包:

buildMode=release

signingConfig=
release_prod_202610

certificateState=
RELEASE_VALID

AppGallery Connect 的 HarmonyOS 包解析错误说明里,debug certificate / debug profile 用于正式发布会被判定为错误;包未签名、Profile 失效、证书过期,也都会阻止发布。

所以 ReleaseGuard 不只判断:

“有签名配置”

而是检查:

构建模式是 release

签名配置名称进入 release 白名单

证书 / Profile 检查结果有效
export class SigningInspector {
  inspect(
    snapshot:
      ReleaseManifestSnapshot
  ): CheckItem[] {
    const releaseMode =
      snapshot.buildMode ===
      'release'

    const releaseSigning =
      snapshot.signingConfig
        .startsWith(
          'release_'
        )

    const certValid =
      snapshot.certificateState ===
      'RELEASE_VALID'

    return [
      {
        key:
          'BUILD_MODE',
        passed:
          releaseMode,
        message:
          snapshot.buildMode
      },
      {
        key:
          'SIGNING_CONFIG',
        passed:
          releaseSigning,
        message:
          snapshot.signingConfig
      },
      {
        key:
          'CERTIFICATE',
        passed:
          certValid,
        message:
          snapshot.certificateState
      }
    ]
  }
}

我不会在文章里把“签名配置名称必须以 release_ 开头”写成 HarmonyOS 系统规则。

这是 ReleaseGuard 自己的团队约定,用来避免拿错构建配置。

真正系统要求仍然以 AppGallery Connect 当前证书、Profile 和签名文档为准。

五、.app 产物存在,和“这个包可以发布”是两回事

本轮构建产物:

releaseguard_1.3.0.app
18.4MB

预检至少确认:

产物存在

扩展名为 .app

文件大小大于 0

构建时间属于本次 task

产物与 Snapshot 版本一致

不能拿昨天的 1.2.9 包,配上今天的 1.3.0 报告一起上传。

所以 Snapshot 会把:

taskId
artifactName
versionName

绑定。

只要不一致:

STALE_ARTIFACT

直接阻断。

六、支持设备也要和市场侧配置做一次对照

当前包:

deviceTypes:
phone
tablet

AppGallery Connect 的包解析错误文档同样列出了:

上传包支持设备
与市场侧选择设备不一致

的失败场景。

ReleaseGuard 当前把 AGC 侧选择同步成发布配置:

expectedDeviceTypes:
phone / tablet

然后做集合比较。

不是简单判断:

deviceTypes.length > 0

因为:

只支持 phone 的包

如果市场版本信息里同时勾了 tablet,也应该在上传前发现。

七、Target API / Compatible API 只做工程门禁,不伪造市场规则

本轮:

targetApi=26
compatibleApi=20

ReleaseGuard 当前只检查:

targetApi >= compatibleApi

以及 targetApi 是否等于本次发布计划里的目标 API。

我不会在文章里写:

AppGallery 永远要求 targetApi=26

因为市场要求会随时间变化。

正确做法是把“当前发布要求”做成团队可更新的规则源:

release-policy.json

而不是把市场政策硬编码进 ArkTS。

八、8 个检查项怎么组成

本轮最终 8 项:

1 bundleName 一致

2 versionName 合法

3 versionCode 递增

4 buildMode=release

5 release signing

6 certificate / profile 有效

7 .app 产物匹配

8 deviceTypes / target API 计划匹配

全部通过:

8 / 8
risk=0

才进入:

PACKAGE_READY

这个状态只代表:

包体和发布身份基础预检通过。

它不代表:

隐私合规已经通过

素材已经准备好

内容分级已经完成

审核一定会通过

后面的 02~06 还要继续收口。

九、DevEco 图里重点看“本地包名”和“AGC 包名”同时出现

开发图:

统一日志:

taskId=
release_20261002_01

bundleName local=
com.example.releaseguard

bundleName agc=
com.example.releaseguard

versionName=
1.3.0

versionCode=
10300

buildMode=
release

signingConfig=
release_prod_202610

certificate=
RELEASE_VALID

artifact=
releaseguard_1.3.0.app

size=
18.4MB

deviceTypes=
phone,tablet

targetApi=
26

checks=
8/8

risk=
0

status=
PACKAGE_READY

这套日志的价值在于:

上传失败时
能马上和 AGC 错误报告对照

而不是只留一句:

build success

十、运行图把“包体身份”集中到一张手机页面

最终运行图:

页面里不展示用户业务,而是展示:

bundleName

版本

发布签名

证书状态

目标 API

设备类型

构建产物

8 项检查

最终:

PACKAGE_READY

这张手机图只是 internal 构建的诊断页。

正式发布包不会把这类发布工具入口暴露给普通用户。

十一、这一篇最值得提前规避的是 AppGallery Connect 的解析错误

当前官方包解析错误说明中,和 01 直接相关的典型错误包括:

bundleName 不一致

Profile 不属于当前应用

使用 debug Profile

Profile 过期

证书异常

包未签名

设备支持信息不匹配

权限与 Profile 不一致

01 先覆盖前六类中的基础部分。

权限与隐私会放到下一篇处理。

我们不可能在本地复制 AppGallery Connect 全部服务端规则,但完全可以把团队最常见、最确定的一批错误挡在上传按钮以前。

十二、下一篇从“包能上传”进入“隐私能对得上”

01 做完以后,包体基础没问题。

接下来最自然的问题是:

module.json5 里声明了什么权限

应用实际为什么要用

隐私政策有没有写

三方 SDK 收集什么

AGC 隐私标签有没有对齐

第二篇会继续同一个 ReleaseGuard,把权限、隐私政策、第三方 SDK 和 AppGallery Connect 隐私标签做成一张可检查矩阵。

不会换项目,也不会重新编号。

十三、把 AppGallery 常见包解析错误做成本地风险映射

ReleaseGuard 不需要复制 AppGallery Connect 的全部服务端校验,但很适合维护一张“团队常见错误 → 本地可检查项”的映射。

当前我先整理这几类:

bundleName 不一致
→ BUNDLE_NAME_MISMATCH

debug Profile
→ DEBUG_PROFILE_FOR_RELEASE

Profile 过期
→ PROFILE_EXPIRED

证书无效
→ CERTIFICATE_INVALID

包未签名
→ UNSIGNED_PACKAGE

设备类型不匹配
→ DEVICE_TYPE_MISMATCH

权限与 Profile 不一致
→ PROFILE_PERMISSION_MISMATCH

这些名字是 ReleaseGuard 的内部风险码,不等于 AppGallery Connect 官方错误码。

真正好处是失败日志更容易读。

比如 AppGallery 返回包解析错误时,发布负责人可以直接在 ReleaseGuard 报告里搜索:

BUNDLE_NAME
SIGNING
PROFILE
DEVICE

快速判断“我们本地本来应该能不能提前发现”。

长期下来,这张映射还能反向推动工具迭代:每次线上出现一个本地漏掉的新问题,就评估是否应该加入 Preflight,而不是下一次继续靠人工记忆。

十四、构建产物还要绑定 hash,防止“报告是新的,包是旧的”

只绑定文件名还不够。

如果 CI 工作目录没有清理干净,完全可能同时存在:

releaseguard_1.3.0.app

两个不同构建产物,名称一样,内容却不一样。

所以 Snapshot 最终还会保存:

artifactSha256
artifactBuiltAt
pipelineRunId

上传前再计算一次 hash:

export interface ArtifactIdentity {
  fileName: string
  sha256: string

  buildTaskId: string
  builtAt: number
}

export class ArtifactIdentityCheck {
  verify(
    expected:
      ArtifactIdentity,
    actual:
      ArtifactIdentity
  ): boolean {
    return (
      expected.fileName ===
        actual.fileName &&
      expected.sha256 ===
        actual.sha256 &&
      expected.buildTaskId ===
        actual.buildTaskId
    )
  }
}

这样可以避免最危险的一类发布事故:

版本页写的是 1.3.0
实际上传的是另一个流水线生成的旧包

这种问题 AppGallery 可能能从版本字段发现一部分,但发布工具自己也应该把产物身份固定下来。

十五、Preflight 要区分 BLOCKER 和 WARNING

不是所有问题都应该一票否决。

例如:

bundleName 不一致
发布签名错误
产物不存在

属于:

BLOCKER

而:

包体比上一版本大 8%
版本说明还没填写
本地报告生成时间超过 30 分钟

更适合:

WARNING

所以检查结果里增加 severity:

export type CheckSeverity =
  'BLOCKER' |
  'WARNING'

export interface ReleaseCheckItem {
  key: string
  severity: CheckSeverity

  passed: boolean
  message: string
}

最终状态规则:

存在 BLOCKER
→ PACKAGE_BLOCKED

只有 WARNING
→ PACKAGE_READY_WITH_WARNING

全部通过
→ PACKAGE_READY

本轮风险项为 0,所以直接进入 PACKAGE_READY。

把严重级别分开后,团队不会因为一个不影响上传的提示被工具卡死,也不会因为所有检查都只是黄色提示而忽略真正的发布错误。

十六、release Profile 和 package 权限最好在第一篇就留一个接口

权限细节放到 02,但 01 已经知道 AppGallery 包解析阶段可能检查 Profile 与包内权限一致性。

所以 PackagePreflightRunner 预留:

profilePermissionSnapshot

01 只确认:

release Profile 已加载

02 再真正做:

module.json5 requestPermissions
⊆
release Profile requested permissions

这样两篇不是两套孤立工具,而是同一个 Runner 持续补充检查。

这也是我设计 ReleaseGuard 系列时最在意的一点:

上架审核不是六张互不相关的 Checklist,而是一份发布上下文逐步变完整。

十七、发布报告必须可机器读取,不只是一张手机页面

手机页适合人看,CI 需要 JSON。

所以最终 Preflight Report 还会输出:

{
  "taskId": "release_20261002_01",
  "status": "PACKAGE_READY",
  "checks": 8,
  "passed": 8,
  "blockers": 0,
  "warnings": 0
}

下一步流水线可以:

status != PACKAGE_READY
→ 禁止上传步骤执行

这样“预检”才真正成为 Release Gate,而不是开发者想看就看、不看也能发的辅助页面。

第六篇会继续把这份 JSON 和审核证据一起打包进 CI。

十八、第一篇最后做十组反向测试

除了正常 8/8,我还固定做:

1 bundleName 故意改错

2 versionCode 回退

3 buildMode 改成 debug

4 signingConfig 指向 debug

5 certificate 标记 expired

6 .app 文件不存在

7 artifact hash 不一致

8 deviceTypes 少 tablet

9 targetApi 与本次计划不一致

10 Snapshot taskId 与产物 pipelineRun 不一致

这十组必须都能被工具明确识别,而且给出不同风险码。

只有“正常包通过 + 异常包能被准确挡住”同时成立,Preflight 才值得放进发布流程。

如果工具只会给正常包打绿勾,却抓不住故意制造的错误,那它只是展示页,不是发布门禁。

十九、上传前还要冻结一次“发布上下文”,避免检查通过后又被改动

预检通过以后,最怕的是:

报告还是 PACKAGE_READY
工程配置却又被人改了

比如检查完成后临时改了:

bundleName
versionCode
deviceTypes
signingConfig

如果流水线继续沿用旧报告,前面的 8/8 已经失去意义。

所以 ReleaseGuard 在 PACKAGE_READY 时生成:

releaseContextId

它由以下信息摘要得到:

Snapshot
Artifact hash
Release policy revision
Signing identity

真正进入上传步骤时,再重新计算一次。

如果 contextId 不一致:

RELEASE_CONTEXT_CHANGED

必须重新预检。

这样“通过检查”和“上传产物”就被绑定在同一份上下文里,不会出现报告和实际包脱节。

二十、ReleaseGuard 的 01 最终收口标准

第一篇最终不是看页面有多少个绿勾,而是看发布链是否满足:

应用身份一致

版本身份一致

发布签名有效

构建产物唯一

设备范围一致

API 计划一致

产物与报告绑定

检查结果可被 CI 读取

只有这些同时成立,PACKAGE_READY 才表示:

这个包值得进入 AppGallery Connect 的下一阶段,而不是“它大概率能上传”。

这也是为什么下一篇可以放心把注意力转到权限和隐私,不再反复回头检查包体身份。

参考资料

  • HarmonyOS 包解析错误码(AppGallery Connect):https://developer.huawei.com/consumer/jp/doc/app/agc-help-harmonyoserror-0000001651912985
  • HarmonyOS 应用 bundleName 配置与 AGC 一致性参考:https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/payment-config-app-identity-info
  • AppGallery Connect 版本历史:https://developer.huawei.com/consumer/fr/doc/app/agc-help-maintain-version-history-0000002236334582
Logo

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

更多推荐