HarmonyOS AppGallery Connect:上架审核前的隐私权限与版本一致性校验【鸿蒙心迹】
这篇不是把审核指南重新抄一遍。我做了一个叫 ReleaseGuard 的上架自检小工具,把最容易反复出错的几项——版本信息、权限声明、权限申请时机、三方 SDK 隐私披露——尽量变成机器能提前发现的问题。

有一次准备提交 HarmonyOS 应用,我在 DevEco Studio 里已经把功能跑通,测试包也没崩,真正到发布阶段却发现“能运行”和“适合提交审核”中间还隔着一层很具体的工程工作。
官方的 HarmonyOS 应用提交页面把发布前测试拆得很清楚:漏洞、隐私、兼容性、稳定性、性能都需要在提交前检查;应用基本信息、素材、隐私保护和审核资质也不是最后点一下按钮就自动完成。对于集成第三方 SDK 的应用,官方上架说明还特别强调,隐私政策需要逐一说明相关 SDK 收集个人信息的目的、方式和范围,否则可能影响审核。
这些要求本身并不难理解,真正麻烦的是它们散落在代码、配置、依赖和发布后台里。开发者经常是功能改了一处,发布信息忘了同步;依赖升级了,隐私声明没补;module.json5 里声明了权限,就误以为“权限这项已经处理完了”。
于是我建了一个小工程 ReleaseGuard。它不是官方审核工具,也不会替代 AppGallery Connect 的最终审核,目标只有一个:在我上传 APP 包以前,把能自动检查的低级问题先拦下来。
本文 Demo 固定使用下面这组数据:bundleName=com.example.releaseguard,versionName=1.4.2,versionCode=1040203。第一次执行一共 9 项检查,只通过 7 项;两个失败项分别是“隐私声明缺少 analytics-sdk@3.2.1”以及“用户同意前触发 CAMERA 权限申请”。修复之后再次执行,结果变成 9/9。
这里的 analytics-sdk@3.2.1 是我为了演示流程使用的虚构依赖名,不对应某个真实 SDK。
一、把“审核退回原因”翻译成工程规则
我没有一开始就写脚本,而是先把过去最容易出错的发布问题分成两类。
第一类是机器很擅长发现的确定性问题,比如版本号是否一致、某个依赖是否存在、声明的权限有哪些、隐私披露清单有没有对应条目、release 包是不是用了调试签名。这些规则非常适合放进 CI 或本地脚本。
第二类是机器只能辅助、人仍然要判断的问题,比如应用业务是否需要某项资质、隐私政策文字是否真正清楚、权限使用目的是否与实际功能匹配、页面是否存在误导性描述。这部分不能因为脚本显示绿色就当成“审核一定通过”。
ReleaseGuard 只处理第一类,第二类永远留给人工复核和官方最终审核。这个边界很重要,否则一个“9/9”很容易制造错误的安全感。
我给每条规则定义了同样的结果结构:
id / title / passed / evidence / message
evidence 是关键。检查脚本说“版本一致”还不够,它要告诉我读取的是哪个文件、值是什么;说“隐私披露缺项”,要列出缺的是哪个依赖。这样失败结果才真正能指导修改。
二、版本一致性是最适合自动检查的一项
HarmonyOS Stage 模型工程里,应用级配置通常位于 AppScope/app.json5,版本名称和版本编码会进入最终应用包。发布时后台填写的信息、你准备对外说明的版本、代码仓里的版本,最好在构建之前就对齐。
我的 Demo 要求这次发布固定为:
bundleName com.example.releaseguard
versionName 1.4.2
versionCode 1040203
只要脚本读出来不是这组值,就直接失败,不继续生成“审核就绪”结果。
这段代码解决什么问题:构建前读取 app.json5,把包名和版本与发布配置进行硬校验。
// scripts/release-check.ts
import fs from 'node:fs'
import JSON5 from 'json5'
interface ReleaseTarget {
bundleName: string
versionName: string
versionCode: number
}
const target: ReleaseTarget = {
bundleName: 'com.example.releaseguard',
versionName: '1.4.2',
versionCode: 1040203
}
const appConfig = JSON5.parse(
fs.readFileSync('AppScope/app.json5', 'utf-8')
)
const actual: ReleaseTarget = {
bundleName: appConfig.app.bundleName,
versionName: appConfig.app.versionName,
versionCode: appConfig.app.versionCode
}
function assertEqual(name: keyof ReleaseTarget): void {
if (actual[name] !== target[name]) {
throw new Error(
`[ReleaseCheck] ${name} mismatch: ` +
`expected=${target[name]}, actual=${actual[name]}`
)
}
}
assertEqual('bundleName')
assertEqual('versionName')
assertEqual('versionCode')
console.info('[ReleaseCheck] version consistency: PASS')
我没有让脚本去猜“下一个版本应该是多少”。版本策略属于团队规则,脚本只负责验证当前这次发布目标。目标值可以来自环境变量、发布配置文件或 CI 参数,重点是它必须只有一个可信来源。
另外,我会在生成 APP 后再做一次包级验证,而不是只读源码配置。原因很简单:真正上传的是构建产物,不是 app.json5。源码正确但构建分支、签名配置或产物选错,一样可能提交错包。
我还会把这组版本信息同步到发布说明草稿里,避免出现“包里是 1.4.2,运营文案还写 1.4.1”的尴尬。这个问题脚本未必能直接判断,但可以让发布配置生成一份 release-meta.json,应用包校验、CI 构建记录和运营侧都读取同一个版本源。对于多人协作项目,这比在群里发一句“这次版本号改成多少”可靠得多。
如果项目同时维护测试、灰度和正式渠道,我还会额外检查渠道对应的 bundle、签名和构建 profile,避免把内部测试产物误当成正式包。版本一致性看起来只是三个字段,实际上它是整条发布链路最适合建立“单一事实来源”的地方。
三、权限声明和权限申请时机是两件事
这是我觉得最容易被混在一起的地方。
module.json5 的 requestPermissions 解决的是“应用声明自己需要哪些权限”。真正涉及用户授权的权限,还要在业务需要时通过访问控制能力查询状态并发起授权请求。官方 FAQ 也把 getSelfPermissionStatus() 与 requestPermissionsFromUser() 作为定位授权状态的关键接口。
换成工程语言就是:配置文件回答“可能会用什么”,运行时代码回答“什么时候真的向用户要”。
如果应用一启动就把 CAMERA、麦克风、相册等权限连续弹出来,即使配置层面没有语法错误,用户体验和隐私合规都很难说合理。更稳妥的方式是把权限申请放到清晰的功能动作后面,例如用户点击“扫码”时再申请 CAMERA。
ReleaseGuard 的 Demo 里,我专门做了一个 PermissionGate。只有隐私提示已经完成,并且用户主动进入扫码功能时,才允许发起相机授权。
这段代码解决什么问题:避免应用启动时提前申请 CAMERA,把授权动作绑定到真实功能触发。
// entry/src/main/ets/service/PermissionGate.ets
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit'
export class PermissionGate {
private consentAccepted: boolean = false
setPrivacyConsent(accepted: boolean): void {
this.consentAccepted = accepted
}
async requestCameraForScan(
context: common.UIAbilityContext
): Promise<boolean> {
if (!this.consentAccepted) {
console.warn('[PermissionGate] CAMERA blocked before consent')
return false
}
const permission: Permissions = 'ohos.permission.CAMERA'
const manager = abilityAccessCtrl.createAtManager()
const status = manager.getSelfPermissionStatus(permission)
if (status === abilityAccessCtrl.PermissionStatus.GRANTED) {
return true
}
const result = await manager.requestPermissionsFromUser(
context,
[permission]
)
return result.authResults.length > 0 && result.authResults[0] === 0
}
}
这里的重点不是“必须先放一个同意开关”这种固定模板,而是权限要有明确业务上下文。你的产品流程可能和我的 Demo 完全不同,但至少应该回答:用户刚刚做了什么操作?为什么此刻需要这项权限?拒绝之后功能如何降级?
如果这些问题说不清楚,脚本即使能检测到 requestPermissions,也不应该给“权限合规”打绿色勾。
四、我把 DevEco Studio 里的检查结果做成可读日志

这张图对应第一次执行 ReleaseGuard 的状态。右边模拟器显示 7/9,通过项包括版本一致性、签名信息和权限声明;失败项有两个:
- 隐私声明缺少
analytics-sdk@3.2.1; - CAMERA 在用户同意前被触发。
底部 HiLog 也打印同样的信息:
[ReleaseCheck] version=1.4.2 code=1040203
[Privacy] analytics-sdk@3.2.1 disclosure missing
[PermissionGate] CAMERA blocked before consent
[ReleaseCheck] finish: 7 / 9 passed
我很在意“模拟器 UI”和“日志”必须说同一件事。开发工具页显示 7/9,日志就不能写 8/9;UI 写 1040203,脚本也必须读取到 1040203。发布类问题最怕多个来源互相矛盾,因为你最后很难判断应该信谁。
所以 ReleaseGuard 的页面本身不重新计算结果,它只渲染脚本/检查服务返回的 CheckResult[]。
五、三方 SDK 披露不能靠人肉记忆
官方上架说明已经把这件事说得很明确:集成第三方 SDK 时,需要在应用隐私政策中逐一明示 SDK 收集个人信息的目的、方式和范围。现实项目里最容易发生的情况是:依赖升级了,开发者知道,隐私文档维护者不知道。
我给项目加了一个自定义清单 release/privacy-sdk.json。注意,这不是 HarmonyOS 官方规定的文件格式,只是我自己的工程约定,目的是让“代码依赖”和“隐私披露”之间可以被脚本比较。
例如:
{
"disclosed": [
"network-core@2.4.0",
"image-cache@1.8.3"
]
}
而依赖清单里存在 analytics-sdk@3.2.1,那脚本就直接把它列为缺项。
这段代码解决什么问题:从依赖集合中找出没有进入隐私披露清单的三方包。
// scripts/privacy-check.ts
import fs from 'node:fs'
import JSON5 from 'json5'
const pkg = JSON5.parse(
fs.readFileSync('oh-package.json5', 'utf-8')
)
const privacy = JSON.parse(
fs.readFileSync('release/privacy-sdk.json', 'utf-8')
)
const dependencyNames = Object.keys(pkg.dependencies ?? {})
const disclosedNames: string[] = privacy.disclosed ?? []
const missing = dependencyNames.filter((name) => {
return !disclosedNames.some((item) => item.startsWith(`${name}@`))
})
if (missing.length > 0) {
console.error(`[Privacy] disclosure missing: ${missing.join(', ')}`)
process.exitCode = 1
} else {
console.info('[Privacy] third-party disclosure: PASS')
}
真实项目里当然不能只比较“包名有没有出现”。还要确认版本、采集信息类型、使用目的、调用场景和供应商说明是否发生变化。这个脚本只负责把“完全忘了披露”这种错误提前抓出来,后面的内容核对仍然必须人工做。
我甚至建议把“依赖变更”纳入代码评审模板。只要 PR 修改了 oh-package.json5 或 lock 文件,就要求顺手确认隐私披露是否需要更新。这样比临近发布时靠一个人从头翻依赖靠谱得多。
六、7 / 9 这个页面的作用,是阻止我带病提交

手机页里我没有做很复杂的视觉设计,核心就是让失败项足够明显。当前结果 7/9,78%,底部“准备提交审核”按钮保持不可用。
两个红色标记对应两个完全不同的问题。
“隐私披露缺项”是静态工程问题,改文档和披露清单;“授权时机错误”是运行时业务问题,需要改代码流程。把它们都叫“隐私问题”太宽泛,真正修复时反而不知道动哪里。
我把检查项拆得尽量具体:
- 版本一致性;
- 签名信息;
- 隐私声明;
- 权限申请声明;
- 权限申请时机;
- 三方 SDK 披露;
- 包信息;
- 发布构建类型;
- 调试标记检查。
这些项目不是官方审核条目的完整映射,只是 ReleaseGuard 的工程检查集合。官方审核规则和资质要求会更新,最终仍以 AppGallery Connect 当前页面、审核指南和审核意见为准。
七、修完以后,我要求 9 / 9 还要有证据
我先在隐私声明中补齐 Demo 依赖 analytics-sdk@3.2.1 的说明,再把 CAMERA 申请移动到用户完成隐私提示、点击“扫码”功能之后。重新执行检查后,ReleaseGuard 才允许显示“审核就绪”。

这次结果和第一次形成一一对应:
versionName=1.4.2、versionCode=1040203没变;- 隐私披露记录在
2026-09-30 11:05补齐; - CAMERA 授权流程在
2026-09-30 11:12调整为按需申请; - 最终检测时间是
2026-09-30 11:18:21; - 9 项全部通过。
我特意在页面里保留“问题修复记录”,而不是只显示一个大绿勾。因为发布版本经常要回溯:这次为什么改了权限流程?为什么隐私政策突然多了一条 SDK?如果只有最终结果,过两周团队自己都忘了。
八、工具脚本最好接在构建前,而不是上传后
如果 ReleaseGuard 只能靠开发者想起来时手动点一下,它很快就会沦为摆设。我更倾向于把确定性检查放到发布构建前。
一个简单的顺序是:
准备 release 参数
→ 运行版本一致性检查
→ 运行依赖 / 隐私披露检查
→ 检查权限声明
→ 构建 release APP
→ 校验产物包信息和签名
→ 真机执行权限场景用例
→ 人工复核隐私政策、资质、截图和应用信息
→ 上传 AppGallery Connect
这样做的好处是,越便宜的问题越早失败。版本号错了,就不要浪费时间跑后面的真机回归;三方 SDK 披露缺项,也不要等包上传后才发现。
官方提交页面同样建议在发布前完成漏洞、隐私、兼容性、稳定性和性能等测试。ReleaseGuard 只是把其中一小部分工程检查左移,并不能替代云测试、云调试或正式审核。
九、我会为每次提交留一个“证据包”
ReleaseGuard 做到后面,我又多加了一件很土但特别有用的事:每次真正准备提审时,把这次检查结果导出成一个小证据包。它不需要很复杂,至少包含应用包名、版本名、版本号、构建时间、依赖摘要、权限列表、检查结果和最终 APP 文件的哈希。
这么做不是为了给审核平台看,而是为了给团队自己留底。发布后的线上问题经常会问:“商店里的 1.4.2 到底是哪次构建?”如果只剩一个版本号,而 CI 当天又跑过好几次,很容易对不上。证据包能把“源码提交—构建产物—提交版本”串成一条线。
我还会把隐私披露清单的摘要放进去,例如本次构建识别到哪些三方依赖、哪些被标记为需要披露、人工复核人是谁。这里并不保存大段隐私政策正文,只保留版本和校验结果,避免证据包反过来变成维护负担。
权限也一样。工具可以把 module.json5 中声明的权限和测试场景记录下来,例如 CAMERA 是在“扫码”按钮触发后申请,而不是启动时申请。等以后某个版本又把权限弹窗提前了,回看两个证据包就能发现行为变化,而不只是凭印象争论“以前是不是也这样”。
这个做法还有一个附带收益:当审核反馈发生时,我可以直接在对应证据包上补一条“审核反馈—修改提交—再次检查”的链路。久而久之,团队会积累出真正属于自己业务的发布知识,而不是每次都从公开文档重新搜索。
十、CI 里的检查必须能阻断错误构建
很多团队其实已经有各种检查脚本,但最后都变成一排绿色日志,因为脚本失败了也不影响构建继续往下跑。我不希望 ReleaseGuard 变成这种“仅供参考”的装饰,所以确定性规则失败时会直接返回非零退出码。
例如版本号不一致、release 构建仍带调试标记、三方依赖完全没有进入披露清单,这些都应该在生成正式产物之前阻断。开发者可以修改规则,也可以在特殊情况下走审批后的 override,但不能默默忽略。
另外,CI 里不要把所有检查塞进一个巨大的 release-check。我更喜欢拆成 version-check、privacy-check、permission-check、package-check,最后再汇总。这样某一项失败时,日志定位更快,规则也更容易由不同负责人维护。
真正需要人工判断的项目则明确输出 REVIEW_REQUIRED,而不是假装 PASS。例如行业资质、隐私文案质量、商店截图是否与当前版本一致,都应该进入发布工单的人工勾选项。自动化最怕边界不清,什么都想判断,最后没人再相信它的结果。
我甚至会要求 CI 保存每一项检查的版本号。因为审核规则、SDK 和脚本都会变化。同样是“9/9”,2026 年 9 月的规则和半年后的规则可能不是同一套。把规则版本一起记录,之后回溯才有意义。
十一、还有三类问题我不会让脚本自动判“通过”
第一类是资质。某些业务面向中国大陆发布时会涉及 ICP、软件著作权或行业资质,具体要求会随业务类型和规则更新。脚本最多提醒“这一栏待人工确认”,不能根据几个关键词就替你判断是否具备合法资质。
第二类是隐私文字质量。脚本能发现“SDK 名字没写”,却很难判断你的收集目的描述是否清晰、是否和真实调用一致,也难判断某个业务是不是存在过度收集。这里必须由产品、法务和开发一起看实际流程。
第三类是页面内容与运营素材。截图、文案、登录流程、会员权益、广告展示、用户生成内容等都会影响审核。它们不是一个 JSON5 文件能覆盖的。
所以我给 ReleaseGuard 的 9/9 定义得非常窄:代表这 9 条工程规则都通过,不代表应用一定通过审核。
十二、上架稳定之后,真正省下来的不是一次审核时间
我以前会把上架问题看成发布当天的工作:版本做完了,开始填后台、补截图、检查隐私。现在更倾向于把它当成开发过程的一部分。
加一个依赖时,就顺手看它会不会影响隐私声明;增加一个权限时,同时设计申请时机和拒绝后的降级;准备版本时,版本号从同一个发布配置生成;真正要上传时,脚本只负责确认团队之前已经做过这些事情,而不是临时补课。
这也是 ReleaseGuard 这个小工具最有价值的地方。它没有什么复杂算法,不过是读几个配置、比几个集合、检查几个状态。但它把“我应该没忘吧”变成了“这里明确还有两项没过”。
上架审核很多时候并不是难在规则看不懂,而是难在工程变化和发布资料长期保持一致。把能自动化的确定性问题交给脚本,把需要判断的部分明确留给人,发布流程反而会简单很多。
最后留一句我现在每次提审前都会看的话:不要把“应用能跑”当成发布完成,真正的 release 版本还应该能解释自己的版本、权限、依赖和隐私边界。
参考资料
- HarmonyOS 应用提交与发布:https://developer.huawei.com/consumer/cn/app/submit
- 华为应用市场审核与处理规则:https://developer.huawei.com/consumer/cn/doc/App/50109
- 上架申请与三方 SDK 隐私说明:https://developer.huawei.com/consumer/cn/doc/HMScore-Guides/app-release-0000001569211185
- 权限申请相关 FAQ:https://developer.huawei.com/consumer/cn/doc/doccenter-dev-faq/faqs-access-control-16
更多推荐


所有评论(0)