HarmonyOS 上架审核闭环实战:权限、签名、测试与材料自检
HarmonyOS 上架审核闭环实战:权限、签名、测试与材料自检
很多应用不是功能做不出来,而是临近上架时被审核问题拖住:权限说明和实际场景对不上,签名配置混用了测试环境,截图材料不完整,弱网或拒绝权限场景一测就崩。
这篇文章按“提交前就把问题暴露出来”的思路整理一套 HarmonyOS 应用上架自检方法。文章不替代官方审核规则,而是给开发者一个工程化检查框架:从 module.json5、签名、功能回归、截图材料到提交前复盘,逐项收口。

1. 本文要解决的审核风险
| 风险 | 典型表现 | 提交前处理 |
|---|---|---|
| 权限不合理 | 申请权限但页面看不到使用场景 | 对照功能入口和权限说明 |
| 签名不一致 | 本地能跑,上架包不可用 | 区分 debug、release、profile |
| 回归不足 | 审核路径出现崩溃或空白 | 做基础功能和异常场景测试 |
| 材料缺失 | 截图、简介、隐私说明不匹配 | 提交前按清单复核 |


2. 上架资料定位与审核边界
| 项目 | 说明 |
|---|---|
| 应用模型 | HarmonyOS Stage 模型 |
| 配置文件 | entry/src/main/module.json5、签名配置、发布 profile |
| 检查对象 | 权限、Ability 声明、图标名称、隐私说明、测试报告、截图材料 |
| 工具 | DevEco Studio、AppGallery Connect、真机测试、日志 |
| 官方资料 | 华为开发者文档中心、AppGallery Connect 上架与审核相关说明 |
参考入口:
- 华为开发者文档中心:https://developer.huawei.com/consumer/cn/doc/
- AppGallery Connect:https://developer.huawei.com/consumer/cn/service/josp/agc/index.html
不同应用品类、目标设备、权限类型和地区要求可能不同。本文提供的是工程自检方法,最终仍要以当前官方上架规则和后台提示为准。
3. 先画出提交包包含什么
上架不是只上传一个 .hap 包。一个可审核的提交包含代码包、签名、配置、权限说明、截图、应用描述、隐私相关材料和测试结论。建议在工程里建立一个发布清单,避免临时靠记忆。
// release/ReleaseChecklist.ets
export interface ReleaseCheckItem {
id: string;
title: string;
owner: string;
passed: boolean;
note: string;
}
export const baseReleaseChecklist: ReleaseCheckItem[] = [
{ id: 'permission', title: '权限申请与使用场景一致', owner: 'client', passed: false, note: '' },
{ id: 'signing', title: '发布签名与 profile 匹配', owner: 'release', passed: false, note: '' },
{ id: 'smoke', title: '核心路径冒烟测试通过', owner: 'qa', passed: false, note: '' },
{ id: 'assets', title: '截图、简介、隐私材料完整', owner: 'product', passed: false, note: '' }
];
这段代码不是让你把发布流程写进应用运行时,而是给团队一个结构化清单。它的输入是检查项,输出是可追踪状态,防止“我以为你检查了”的责任空洞。
4. 权限要能对应到真实功能入口
权限审核最怕“申请了但看不到为什么用”。如果应用申请定位、相机、麦克风、文件等敏感权限,页面上必须有清晰的使用场景,用户拒绝后也不能直接崩溃。
下面是一个权限声明示例,实际权限名称以当前 SDK 和官方文档为准:
// entry/src/main/module.json5
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:app_icon",
"label": "$string:app_name",
"exported": true
}
],
"requestPermissions": [
{
"name": "ohos.permission.LOCATION",
"reason": "$string:permission_location_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}
]
}
}
代码解释:
| 点 | 说明 |
|---|---|
| 职责边界 | module.json5 只声明权限和使用场景 |
| 输入约束 | 权限原因要写在资源文件中,便于多语言和审核查看 |
| 避免的问题 | 防止权限声明和页面功能不一致 |
| 下一层连接 | 页面层要处理授权、拒绝和再次引导 |
不要为了“以后可能用”提前申请权限。审核和用户体验都更关注最小化原则。
5. 页面层必须处理拒绝权限
权限通过不代表应用稳定。审核人员或用户可能拒绝权限,如果页面只写成功路径,就会出现空白、按钮无响应或崩溃。
// common/permission/PermissionGuard.ets
export type PermissionState = 'granted' | 'denied' | 'unknown';
export class PermissionGuard {
static explainLocationUsage(): string {
return '用于展示附近服务和路线推荐,不会在未使用相关功能时持续定位。';
}
static canEnterLocationFeature(state: PermissionState): boolean {
return state === 'granted';
}
static deniedMessage(): string {
return '未获得定位权限,仍可浏览基础内容;需要附近服务时可在系统设置中开启。';
}
}
这段代码把权限文案和进入条件集中管理。它防止页面到处写不同版本的解释,也让拒绝权限后的体验保持一致。下一层页面只需要根据 PermissionState 展示正常内容或替代内容。
@Component
struct LocationFeatureEntry {
@State private permissionState: PermissionState = 'unknown';
build() {
Column({ space: 12 }) {
Text('附近服务')
.fontSize(22)
.fontWeight(FontWeight.Bold)
Text(PermissionGuard.explainLocationUsage())
.fontSize(14)
.fontColor('#667085')
Button('进入附近服务')
.enabled(PermissionGuard.canEnterLocationFeature(this.permissionState))
if (this.permissionState === 'denied') {
Text(PermissionGuard.deniedMessage())
.fontSize(13)
.fontColor('#B42318')
}
}
.padding(16)
}
}
这段组件代码的重点是拒绝权限也有可读内容。审核时如果拒绝权限,页面仍然解释清楚功能边界,不会把用户卡死。
6. 签名配置不要混用测试环境
签名问题经常在最后阶段才暴露。开发阶段的 debug 配置、测试 profile、正式发布 profile 如果混在一起,容易出现本地能运行、提交包不符合发布要求的问题。
建议把发布签名检查写成独立脚本或清单,至少核对三件事:
| 检查项 | 说明 |
|---|---|
| 包名 | 与 AGC 后台应用信息一致 |
| 证书 | 使用发布证书,不混用 debug |
| profile | 与目标应用、设备类型和发布渠道匹配 |
可以准备一个发布前配置摘要,不把敏感信息写进文章或日志:
// release/SigningSummary.ets
export interface SigningSummary {
bundleName: string;
buildMode: 'debug' | 'release';
profileType: 'debug' | 'release';
checkedAt: string;
}
export function assertReleaseSigning(summary: SigningSummary): string[] {
const errors: string[] = [];
if (summary.buildMode !== 'release') {
errors.push('当前不是 release 构建模式');
}
if (summary.profileType !== 'release') {
errors.push('当前 profile 不是发布类型');
}
if (summary.bundleName.length === 0) {
errors.push('bundleName 为空');
}
return errors;
}
这段检查代码的边界是发布摘要,不读取证书内容,也不输出敏感路径。它防止提交前忘记切换构建模式。实际团队可以把它接到发布脚本或人工检查表里。
7. 冒烟测试覆盖用户会走的路径
审核人员不会帮你测所有边界,但常见路径出问题很容易被发现。提交前至少覆盖首页、登录、核心功能、权限拒绝、弱网和返回路径。
// release/SmokeCase.ets
export interface SmokeCase {
name: string;
steps: string[];
expected: string;
}
export const releaseSmokeCases: SmokeCase[] = [
{
name: '首次启动进入首页',
steps: ['清理应用数据', '冷启动应用', '等待首页出现'],
expected: '首页可见,无白屏,无崩溃'
},
{
name: '拒绝定位权限后继续使用',
steps: ['进入附近服务', '拒绝权限', '返回首页'],
expected: '页面给出解释,基础功能可继续使用'
},
{
name: '弱网打开核心页面',
steps: ['开启弱网或断网', '进入核心页面', '点击返回'],
expected: '出现兜底内容或错误提示,不闪退'
}
];
这段代码把测试用例结构化,便于复制到测试文档或发布记录里。它防止冒烟测试只说“测过了”,但没有步骤和预期结果。
8. 截图材料要和实际功能一致
截图材料不是装饰图。上架截图、应用简介、权限说明、隐私描述如果和实际功能不一致,容易降低审核通过率,也会误导用户。
建议按页面维度整理材料:
// release/SubmitAssetPlan.ets
export interface SubmitAsset {
pageName: string;
screenshot: string;
description: string;
relatedPermission?: string;
}
export const submitAssets: SubmitAsset[] = [
{
pageName: '首页',
screenshot: 'home_light.png',
description: '展示主要功能入口和推荐内容'
},
{
pageName: '附近服务',
screenshot: 'nearby_service.png',
description: '展示基于位置的附近服务',
relatedPermission: 'LOCATION'
}
];
这段材料计划把截图和功能说明绑定起来。它防止截图展示了一个能力,但权限说明里没有提;也防止权限申请了,但材料里看不到对应功能。
9. 隐私与权限文案要说人话
权限原因不要写成“用于提升体验”这种空泛句子。更好的写法是说明具体功能、使用时机和不使用时的影响。
| 弱文案 | 更清晰的写法 |
|---|---|
| 用于提升用户体验 | 用于在你打开附近服务时展示周边地点 |
| 用于功能使用 | 用于拍摄头像或上传反馈图片 |
| 用于数据统计 | 用于记录崩溃信息,帮助定位稳定性问题 |
文案检查时可以问三个问题:
- 用户能不能看懂为什么要这个权限?
- 拒绝后还能不能继续使用非相关功能?
- 应用是否只在对应场景触发权限申请?
10. 提交前做一次发布复盘
发布复盘不是写长报告,而是把风险项收口。下面这个函数可以帮助团队把清单结果转成可读摘要。
// release/ReleaseReportBuilder.ets
import { ReleaseCheckItem } from './ReleaseChecklist';
export function buildReleaseReport(items: ReleaseCheckItem[]): string {
const failed = items.filter(item => !item.passed);
if (failed.length === 0) {
return '发布自检通过,权限、签名、测试和材料均已确认。';
}
return failed
.map(item => `${item.title} 未通过,负责人:${item.owner},备注:${item.note}`)
.join('\n');
}
这段代码的输入是检查项列表,输出是发布摘要。它防止提交前还有未关闭的问题。真实项目中可以把结果写入 Markdown、飞书文档或发布记录。
11. 常见审核问题排查
| 现象 | 可能原因 | 检查方法 | 修复建议 |
|---|---|---|---|
| 权限相关被驳回 | 权限原因不清楚或场景不匹配 | 对照 module.json5 和功能入口 | 删除无用权限,补充真实使用说明 |
| 提交包不可用 | 签名或 profile 混用 | 核对构建模式、证书、包名 | 重新使用发布配置打包 |
| 审核时页面空白 | 弱网或接口异常未兜底 | 断网打开核心页面 | 增加缓存、错误提示和重试 |
| 截图与实际不一致 | 使用了旧版本截图 | 对照当前 release 包逐页检查 | 重新截取真实页面 |
| 隐私说明被质疑 | 文案过于笼统 | 检查权限和数据使用说明 | 写清楚用途、时机和影响 |
12. 提交前验收清单
module.json5中的权限都有真实功能入口。- 敏感权限有清晰 reason 文案和拒绝后的页面处理。
- 发布包使用 release 构建模式和正式 profile。
- 首页、登录、核心功能、权限拒绝、弱网都做过冒烟测试。
- 截图来自当前提交版本,不使用旧图。
- 应用简介、隐私说明、权限说明之间没有冲突。
- 提交前留存发布清单和测试结论。
这份清单建议由开发、测试、产品分别确认,不要只让一个人从头勾到尾。开发负责配置和签名,测试负责功能与异常路径,产品负责截图、简介、隐私说明和权限文案。每个角色只确认自己能负责的内容,最后再由发布负责人汇总。这样审核被驳回时,团队能快速定位是代码问题、配置问题、测试遗漏,还是材料问题。
| 负责人 | 检查内容 | 交付物 |
|---|---|---|
| 开发 | 权限声明、Ability 配置、签名构建 | 发布包和配置截图 |
| 测试 | 冒烟路径、弱网、拒绝权限、返回路径 | 测试记录和问题截图 |
| 产品 | 应用简介、截图、隐私说明、权限文案 | 上架材料清单 |
| 发布负责人 | 汇总风险项,确认是否提交 | 最终发布记录 |
13. 把审核变成发布前的固定动作
HarmonyOS 应用上架审核不是最后一步才开始的事情。权限要从功能设计阶段就考虑,签名要和发布环境保持一致,测试要覆盖审核可能走到的路径,截图和说明材料要与真实功能对应。把这些内容做成固定闭环,团队每次发布都按同一套清单检查,才能减少临近上线时的返工。
更实用的做法是给每次提交保留一份发布复盘:本次新增了哪些权限、变更了哪些页面、替换了哪些截图、修复了哪些审核风险。下一次版本迭代时,不需要重新理解整套上架流程,只要对比上一份复盘,就能快速看出风险增量。
| 复盘项 | 记录内容 |
|---|---|
| 权限变化 | 新增、删除或修改的权限与对应页面 |
| 包体变化 | 包名、版本号、签名、profile 是否变化 |
| 功能变化 | 影响审核路径的新增页面或核心流程 |
| 材料变化 | 截图、简介、隐私说明是否同步更新 |
| 遗留风险 | 暂未解决但已确认不影响提交的问题 |
更多推荐



所有评论(0)