HarmonyOS 权限申请避坑:声明、弹窗、审核说明一次讲清
HarmonyOS 权限申请避坑:声明、弹窗、审核说明一次讲清

应用上架审核里,权限问题很常见:功能里确实需要定位,但 module.json5 没声明清楚;用户点击“附近门店”才需要定位,应用却一启动就弹权限;相册权限只是上传头像,却在审核说明里写成“提升体验”。这些问题不一定是代码跑不通,而是权限申请链路不完整。
这篇文章只讲一件事:HarmonyOS 应用申请权限时,如何把业务场景、配置声明、运行时弹窗、拒绝处理和 AGC 审核说明串起来。
一、资料定位:权限不是只写一行配置
| 类型 | 资料/能力 | 本文使用点 |
|---|---|---|
| 官方权限文档 | 访问控制开发概述 | 权限类型、授权边界 |
| 官方申请权限 | 向用户申请授权 | 运行时申请、授权结果处理 |
| 官方配置 | module.json5 配置文件 | 权限声明位置 |
| 官方上架 | AppGallery Connect 提交审核 | 审核资料、版本提交 |
本文以 HarmonyOS 5.0.0+、Stage 模型、ArkTS 为边界。示例围绕“附近路线推荐”功能:只有用户点击附近路线时才申请定位权限。
二、合规流程:先有场景,再申请权限

权限申请按六步走:
- 确认业务场景。
- 在配置里声明权限。
- 给用户说明使用目的。
- 在用户触发功能时运行时申请。
- 处理拒绝和永久拒绝。
- 上架前复查权限与功能是否一致。
不要把权限申请写成“进入首页就弹”。这会让用户反感,也容易被审核认为申请时机不合理。
三、权限合规架构

推荐目录:
entry/src/main/ets/
├── pages/nearby/
│ └── NearbyRoutePage.ets
├── permission/
│ ├── PermissionReason.ets
│ ├── PermissionGuard.ets
│ └── DeniedHandler.ets
└── service/location/
└── NearbyLocationService.ets
| 文件 | 职责 |
|---|---|
module.json5 |
声明应用会使用哪些权限 |
PermissionReason |
管理给用户看的权限说明 |
PermissionGuard |
执行检查和运行时申请 |
DeniedHandler |
处理拒绝后的降级路径 |
NearbyLocationService |
真正使用定位能力 |
权限逻辑不要散落在每个按钮里。统一封装后,上架前检查也更容易。
四、先做权限清单:能不用就不用,能按需就按需
| 功能 | 权限 | 申请时机 | 拒绝后策略 |
|---|---|---|---|
| 附近路线推荐 | 位置权限 | 点击“查看附近” | 手动选择城市 |
| 扫码导入路线 | 相机权限 | 点击“扫码” | 输入路线码 |
| 上传头像 | 相册/媒体权限 | 点击“更换头像” | 使用默认头像 |
| 语音备注 | 麦克风权限 | 点击“录音” | 文本输入 |
| 网络请求 | 网络访问 | 应用使用期间 | 提示网络不可用 |
这张表最好在开发前就写。它能避免两个问题:申请了不用的权限,或者用了权限却没说明业务场景。
五、module.json5:声明要准确,不要贪多
下面示例只声明“附近路线”需要的定位权限。具体权限名要以项目目标 API 和官方权限列表为准。
{
"module": {
"name": "entry",
"type": "entry",
"requestPermissions": [
{
"name": "ohos.permission.LOCATION",
"reason": "$string:permission_location_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}
]
}
}
代码解释:
name必须和实际能力匹配,不要把暂时不用的权限提前声明。reason要写给用户能理解的业务原因,不要写“应用需要权限”。usedScene要和实际使用 Ability 对应。when表示使用场景,具体取值以官方文档和权限类型要求为准。
权限声明不是越全越保险。声明越多,审核越会关注你是否真的需要。
六、权限说明:用户要知道为什么此刻申请
export interface PermissionReason {
permission: string;
title: string;
message: string;
fallback: string;
}
export const LOCATION_REASON: PermissionReason = {
permission: 'ohos.permission.LOCATION',
title: '需要定位权限',
message: '用于根据当前位置推荐附近路线,不会在后台持续获取位置。',
fallback: '如果拒绝授权,可以手动选择城市继续使用路线搜索。'
};
代码解释:
message要讲业务场景,不讲空话。fallback告诉用户拒绝后还能怎么用。- 不要承诺做不到的事,比如“绝不收集任何信息”。
- 权限说明和隐私政策里的描述要一致。
如果用户点击“附近路线”,此时弹出定位说明是合理的;如果刚打开应用就弹,用户很难理解。
七、运行时申请:用户触发功能后再弹
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common';
export class PermissionGuard {
private atManager = abilityAccessCtrl.createAtManager();
async ensurePermission(
context: common.UIAbilityContext,
permission: Permissions
): Promise<boolean> {
const grantStatus = await this.atManager.checkAccessToken(
context.applicationInfo.accessTokenId,
permission
);
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
return true;
}
const result = await this.atManager.requestPermissionsFromUser(context, [permission]);
return result.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}
}
代码解释:
- 先检查授权状态,已经授权就不重复弹窗。
requestPermissionsFromUser()要在用户触发相关功能后调用。- 返回值只表达是否可继续执行,不在这里写业务降级。
- 权限类型使用
Permissions,减少字符串写错风险。
真实项目里要根据 API 版本确认导入路径和返回结构。权限代码最怕复制旧版本示例后不复测。
八、页面调用:拒绝后也要有路可走
@Entry
@Component
struct NearbyRoutePage {
private permissionGuard: PermissionGuard = new PermissionGuard();
@State private message: string = '点击按钮查看附近路线';
build() {
Column({ space: 16 }) {
Text(this.message)
.fontSize(18)
Button('查看附近路线')
.onClick(() => {
this.openNearbyRoutes();
})
Button('手动选择城市')
.onClick(() => {
this.message = '已切换为手动城市选择模式';
})
}
.padding(24)
}
private async openNearbyRoutes(): Promise<void> {
const context = getContext(this) as common.UIAbilityContext;
const granted = await this.permissionGuard.ensurePermission(
context,
'ohos.permission.LOCATION'
);
if (!granted) {
this.message = '未授权定位,可手动选择城市继续使用';
return;
}
this.message = '正在获取附近路线...';
}
}
代码解释:
- 权限申请发生在用户点击“查看附近路线”之后。
- 拒绝后不阻断整个页面,而是提供“手动选择城市”。
- 页面只关心业务结果,不关心权限底层细节。
- 审核时能看出权限和功能存在明确对应关系。
权限合规的一个核心点是“克制”。不是所有功能都必须拿权限,也不是拒绝后就让用户寸步难行。
九、拒绝处理:不要循环弹窗
export class DeniedHandler {
static buildLocationDeniedTips(): string {
return '定位权限未开启,附近路线将无法自动推荐。你可以手动选择城市继续使用。';
}
static shouldShowGuide(rejectCount: number): boolean {
return rejectCount <= 1;
}
}
代码解释:
- 拒绝后给出功能影响和替代方案。
- 不要循环弹权限弹窗,会严重影响体验。
rejectCount可以存在本地,用于控制引导频率。- 永久拒绝场景可以引导用户去系统设置,但不要强迫。
很多审核问题不是权限本身,而是申请方式太激进。用户拒绝一次后,应该尊重这个结果。
十、AGC 审核说明怎么写
权限审核说明建议写成“功能 + 时机 + 用途 + 拒绝后影响”。
| 权限 | 推荐说明 |
|---|---|
| 定位 | 用户点击“查看附近路线”时申请,用于推荐当前位置附近路线;拒绝后可手动选择城市 |
| 相机 | 用户点击“扫码导入路线”时申请,用于扫描路线二维码;拒绝后可手动输入路线码 |
| 相册 | 用户点击“更换头像/上传图片”时申请,用于选择本地图片;拒绝后使用默认图片 |
| 麦克风 | 用户点击“语音备注”时申请,用于录制路线备注;拒绝后可使用文字输入 |
不要写这种说明:
为了提升用户体验,需要申请定位权限。
这句话太空,审核人员看不出权限和功能的对应关系。
十一、验证流程
1. 首次安装应用,进入首页,确认不会立即弹定位权限。
2. 点击“查看附近路线”,确认先出现合理说明或系统授权弹窗。
3. 点击拒绝,确认页面提供手动选择城市方案。
4. 再次点击功能,确认不会无限骚扰式弹窗。
5. 授权后进入功能,确认定位能力只在对应功能里使用。
6. 检查隐私政策和 AGC 审核说明是否一致。
建议保留测试截图:
| 截图 | 用途 |
|---|---|
| 权限触发入口 | 证明不是启动即申请 |
| 系统授权弹窗 | 证明权限名和时机 |
| 拒绝后的降级页面 | 证明不强迫授权 |
| AGC 权限说明 | 证明审核材料完整 |
十二、常见问题排查
| 现象 | 可能原因 | 检查方法 | 修复建议 |
|---|---|---|---|
| 审核说权限用途不清 | 说明太泛 | 查看 AGC 权限说明 | 写清功能、时机、用途 |
| 用户一进应用就被弹窗 | 申请时机过早 | 搜索启动生命周期 | 移到用户触发后 |
| 拒绝后功能崩溃 | 没有降级路径 | 拒绝权限测试 | 提供手动输入或替代能力 |
| 配置了权限但申请失败 | 权限名或 usedScene 不匹配 | 检查 module.json5 | 对照官方权限文档 |
| 隐私政策和弹窗不一致 | 多处文案各写各的 | 对比文案 | 统一 PermissionReason |
十三、上架前验收清单
| 检查项 | 是否完成 |
|---|---|
| 每个权限都有明确业务场景 | |
| 没有声明暂时不用的敏感权限 | |
| 权限申请发生在用户触发功能后 | |
| 拒绝权限后有可用降级方案 | |
| 权限说明、隐私政策、AGC 审核说明一致 | |
| 首次安装、拒绝、授权、系统设置关闭都测试过 | |
| 日志和埋点不记录敏感权限数据 |
权限类问题建议在提交审核前单独做一轮“干净安装测试”。不要用已经授权过的开发机直接判断流程是否合规,因为系统可能不会再次弹窗,很多首次授权问题会被掩盖。测试时至少覆盖首次安装、拒绝一次、拒绝后再次点击、系统设置关闭权限、重新授权这几条路径。
审核材料也要和代码同步更新。只要新增、删除或调整了敏感权限,AGC 的权限说明、应用内弹窗说明、隐私政策都要一起检查。三处只要有一处说法不一致,就可能被认为用途不清或告知不充分。
十四、总结
HarmonyOS 权限申请能否顺利通过审核,不只看代码能不能调通,更看整条链路是否合理:
- 业务确实需要。
- 配置声明准确。
- 用户触发后再申请。
- 拒绝后有替代方案。
- 审核说明写清楚。
权限不是越多越强,而是越克制越稳。把“为什么要申请、什么时候申请、拒绝怎么办”讲清楚,上架风险会小很多。
更多推荐



所有评论(0)