HarmonyOS 敏感权限动态申请实战:atManager 申请、状态校验与引导去设置
·
HarmonyOS 敏感权限动态申请实战:atManager 申请、状态校验与引导去设置
前言
定位、相机、麦克风、通讯录这些敏感权限,HarmonyOS 不允许应用默认拥有,必须在运行时向用户申请,且用户拒绝后不能反复弹窗骚扰。很多应用一启动就"相机闪退"或"定位拿不到",根因就是没走标准的动态权限申请流程。本文基于 ability_accessCtrl(AtManager),给出从"检查已授权 → 申请 → 处理拒绝 → 引导去系统设置"的完整闭环代码。
问题描述
典型故障:
- 直接
camera.getCameraManager()不申请权限,系统抛201错误,应用崩溃。 - 用户点了"拒绝且不再提示",应用每次都弹申请框,用户恼火,且系统可能直接禁止再弹。
- 申请了
ohos.permission.LOCATION,但没申请ohos.permission.APPROXIMATELY_LOCATION或PRECISE_LOCATION子权限,定位返回空。 - 权限被拒后,界面还停留在"等待授权"状态,没有给用户"去设置页手动开启"的出口。
核心要求:先查后申、按权限组申请、拒绝后降级处理、提供设置页入口。
细节解析
1. 权限分类
- system_grant(系统授权):安装即授权,无需弹窗(如
INTERNET)。 - user_grant(用户授权):必须运行时弹窗申请(如
CAMERA、LOCATION、MICROPHONE)。本文聚焦这一类。 - 部分权限有子权限:定位分
ohos.permission.APPROXIMATELY_LOCATION(模糊)与ohos.permission.PRECISE_LOCATION(精确),申请精确必须同时声明模糊。
2. 标准三步
atManager.checkAccessToken(tokenId, permission)查当前授权状态。GRANTED直接用;否则atManager.requestPermissionsFromUser(context, [perms])弹窗申请。- 解析
PermissionRequestResult:authResults[i] === 0为授权;非 0 看dialogShownResults判断是否还能再弹。
3. 拒绝后的处理
- 如果
dialogShownResults[i] === 0(弹过且被拒)→ 不要再弹,应提示用户并引导去系统设置:ability_accessCtrl.openPermissionSettings(context)(或requestPermissionOnSetting)。 - 如果
dialogShownResults[i] === -1(用户选了"不再提示")→ 只能去设置页。
4. module.json5 声明
申请的权限必须先在 module.json5 的 requestPermissions 里声明,否则 requestPermissionsFromUser 直接失败。
示例代码
权限工具封装
// src/main/ets/permission/PermissionUtil.ets
import { ability_accessCtrl, Permissions, PermissionRequestResult } from '@kit.AbilityKit'
import { common } from '@kit.AbilityKit'
import { BusinessError } from '@kit.BasicServicesKit'
export class PermissionUtil {
private static atManager = ability_accessCtrl.createAtManager()
// 检查单个权限是否已授权
static async check(permission: Permissions): Promise<boolean> {
const ctx = getContext() as common.UIAbilityContext
const tokenId = ctx.applicationInfo.accessTokenId
const status = await this.atManager.checkAccessToken(tokenId, permission)
return status === ability_accessCtrl.GrantStatus.PERMISSION_GRANTED
}
// 申请一组权限,返回每个权限是否成功
static async request(permissions: Permissions[]): Promise<Record<string, boolean>> {
const ctx = getContext() as common.UIAbilityContext
const result: Record<string, boolean> = {}
try {
const res: PermissionRequestResult = await this.atManager.requestPermissionsFromUser(ctx, permissions)
permissions.forEach((p, i) => {
result[p] = res.authResults[i] === 0
})
return result
} catch (err) {
const e = err as BusinessError
console.error(`申请权限失败 code=${e.code} msg=${e.message}`)
permissions.forEach(p => result[p] = false)
return result
}
}
// 是否还能再次弹窗(false 表示用户选了"不再提示",只能去设置)
static async canAskAgain(permission: Permissions): Promise<boolean> {
const ctx = getContext() as common.UIAbilityContext
try {
const res = await this.atManager.requestPermissionOnSetting(ctx, [permission])
return res[0] === 0
} catch {
return false
}
}
// 引导去系统设置页开启权限
static async openSettings() {
const ctx = getContext() as common.UIAbilityContext
try {
await this.atManager.openPermissionSettings(ctx)
} catch (err) {
const e = err as BusinessError
console.error(`打开设置失败 code=${e.code}`)
}
}
}
在页面里使用(以相机为例)
// src/main/ets/pages/CameraPage.ets
import { Permissions } from '@kit.AbilityKit'
import { PermissionUtil } from '../permission/PermissionUtil'
import { promptAction } from '@kit.ArkUI'
@Entry
@Component
struct CameraPage {
@State ready: boolean = false
async ensureCamera(): Promise<boolean> {
const perm: Permissions = 'ohos.permission.CAMERA'
if (await PermissionUtil.check(perm)) {
this.ready = true
return true
}
const res = await PermissionUtil.request([perm])
if (res[perm]) {
this.ready = true
return true
}
// 被拒:尝试引导去设置
const canAsk = await PermissionUtil.canAskAgain(perm)
if (canAsk) {
// 还能弹:再次申请(实际多发生在用户点了"拒绝"但没选不再提示)
const r2 = await PermissionUtil.request([perm])
if (r2[perm]) { this.ready = true; return true }
}
// 彻底没权限:提示去设置
AlertDialog.show({
message: '需要相机权限才能拍照,请在设置中开启',
primaryButton: { action: () => PermissionUtil.openSettings() },
secondaryButton: { action: () => promptAction.showToast({ message: '已取消' }) }
})
return false
}
build() {
Column({ space: 16 }) {
if (this.ready) {
Text('相机已就绪,可以拍照').fontSize(18)
} else {
Button('申请相机权限并拍照').onClick(() => this.ensureCamera())
}
}
.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
module.json5 必须声明
{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.CAMERA", "reason": "$string:camera_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } },
{ "name": "ohos.permission.APPROXIMATELY_LOCATION", "reason": "$string:loc_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inUse" } },
{ "name": "ohos.permission.PRECISE_LOCATION", "reason": "$string:loc_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inUse" } }
]
}
}
总结
- 先声明后申请:
module.json5的requestPermissions不写,运行时申请直接报201。 - 先
checkAccessToken再request:已授权就别弹窗,减少打扰。 - 子权限成对申请:精确定位要同时声明
APPROXIMATELY_LOCATION+PRECISE_LOCATION。 - 拒绝后别硬弹:看
authResults与dialogShownResults,用户选"不再提示"就openPermissionSettings引导去系统设置。 - 给用户出口:被拒界面要提供"去设置开启"按钮,否则功能就卡死在不可用状态。
更多推荐
所有评论(0)