开发者技术支持-鸿蒙应用权限申请分析与最佳实践
1.1 问题说明
在鸿蒙原生应用开发中,权限管理是确保应用功能正常运作的关键环节。使用 Normal 级别证书开发的应用,其可申请的权限分为系统授权权限和用户授权权限两类。开发者在配置和使用权限时,若不区分这两类权限的配置要求和申请流程,可能导致权限配置错误、功能无法正常使用,甚至应用闪退。本文将详细分析两类权限的区别、配置规范及配套的权限检查与申请代码实现。
1.2 原因分析
• 权限分类机制
鸿蒙系统将权限分为 system_grant(系统授权)和 user_grant(用户授权)两类:
1.系统授权权限:安装时自动授予,如网络权限(ohos.permission.INTERNET)
2.用户授权权限:运行时需动态申请,如定位权限(ohos.permission.LOCATION)
• 配置差异导致的错误
两类权限在module.jsons中的配置要求不同:
1.系统授权权限:仅需申明name字段
2.用户授权权限:必须完整配置reason和usedscene,否则无法通过应用上架审核或运行时授权失败
• 权限检查缺失风险
直接调用涉及敏感权限的 API 前未进行权限状态检查,会导致:
1.权限未授予时调用 API 引发安全异常
2.用户拒绝授权后无降级处理逻辑,功能不可用且无提示
• 动态申请流程不完整
用户授权权限需要先检查状态,未授权时需主动调用 `requestPermissionsFromUser` 触发系统弹窗,缺失此环节将导致权限永远无法获取。
1.3 解决思路
• 明确权限分类
1.查阅官方文档确认目标权限属于 system_grant 还是 user_grant
2.根据分类在 `module.json5` 中进行差异配置
• 封装统一的权限检查工具
1.创建可复用的权限状态检查函数
2.支持同步/异步获取当前授权状态
• 实现权限申请流程标准化
1.封装权限请求函数,处理用户授权弹窗逻辑
2.统一处理授权成功/失败的回调与错误提示
• 建立权限使用规范
1.调用敏感 API 前必须经过权限检查
2.用户拒绝时引导至系统设置开启权限
• 数据量监控与预校验
开发阶段通过 JSON.stringify(data).length 检查数据大小,提前发现超限风险。
1.4 解决方案
a.module.json5 权限配置规范
{
"requestPermissions": [
// 用户授权权限示例(需完整配置)
{
"name": "ohos.permission.LOCATION",
"reason": "$string:permissionsReason", // 必填:使用权限的理由
"usedScene": { // 必填:使用场景
"abilities": [
"EntryAbility"
],
"when": "always" // 调用时机:always/inuse
}
},
{
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"reason": "$string:permissionsReason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "always"
}
},
// 系统授权权限示例(仅需name)
{
"name": "ohos.permission.INTERNET" // 系统权限,安装时自动授予
}
]
}
b.权限状态检查工具函数
import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 检查单个权限的授予状态
* @param permission 权限名称
* @returns 授权状态 GrantStatus
*/
export async function checkPermissionGrant(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
// 获取应用程序的accessTokenID
let tokenId: number = 0;
try {
let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
);
let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
tokenId = appInfo.accessTokenId;
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to get bundle info: ${err.code}, ${err.message}`);
}
// 校验应用是否被授予权限
try {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let grantStatus: abilityAccessCtrl.GrantStatus = await atManager.checkAccessToken(tokenId, permission);
return grantStatus;
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to check access token: ${err.code}, ${err.message}`);
return abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
}
}
/**
* 检查权限是否已授予(布尔值版本)
* @param permissions 权限名称
* @returns true=已授权,false=未授权
*/
export async function checkPermissions(permissions: Permissions): Promise<boolean> {
let grantStatus: abilityAccessCtrl.GrantStatus = await checkPermissionGrant(permissions);
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}
c. 用户授权权限动态申请
import { common, abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 向用户申请权限(弹窗请求)
* @param permissions 需要申请的权限数组
* @param context UIAbility上下文
* @returns Promise<number> 0=全部授权成功,其他=失败
*/
export function reqPermissionsFromUser(
permissions: Array<Permissions>,
context: common.UIAbilityContext
): Promise<number> {
return new Promise(async (resolve, reject) => {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
try {
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
const data = await atManager.requestPermissionsFromUser(context, permissions);
console.info(`requestPermissionsFromUser result: ${JSON.stringify(data)}`);
let grantStatus: Array<number> = data.authResults;
// 检查所有权限是否都授予
for (let i = 0; i < grantStatus.length; i++) {
if (grantStatus[i] !== 0) { // 0 表示授予
// 用户拒绝授权,引导至系统设置
console.warn(`Permission ${permissions[i]} denied`);
reject(new Error('用户拒绝授权,请在系统设置中手动开启权限'));
return;
}
}
// 所有权限都授予成功
resolve(0);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to request permissions: ${err.code}, ${err.message}`);
reject(err);
}
});
}
d. 完整的使用示例(以定位权限为例)
import { Permissions } from '@kit.AbilityKit';
// 定义需要检查的权限
const LOCATION_PERMISSIONS: Permissions[] = [
'ohos.permission.LOCATION',
'ohos.permission.APPROXIMATELY_LOCATION'
];
/**
* 定位功能权限处理完整流程
*/
async function handleLocationPermission(context: common.UIAbilityContext): Promise<boolean> {
try {
// 1. 逐个检查权限状态
for (const perm of LOCATION_PERMISSIONS) {
const isGranted = await checkPermissions(perm);
if (!isGranted) {
console.info(`Permission ${perm} not granted, requesting...`);
// 2. 未授权则发起申请
await reqPermissionsFromUser(LOCATION_PERMISSIONS, context);
// 3. 申请后重新确认状态
for (const checkPerm of LOCATION_PERMISSIONS) {
const finalStatus = await checkPermissions(checkPerm);
if (!finalStatus) {
console.error(`Permission ${checkPerm} still denied after request`);
return false;
}
}
break; // 权限已成功申请
}
}
console.info('All location permissions granted');
return true;
} catch (error) {
console.error('Permission request failed:', error);
// 可在此处展示引导弹窗,提示用户手动开启权限
return false;
}
}
1.5 总结
• 问题说明:鸿蒙应用中 Normal 级别证书的权限分为 system_grant(系统授权)和 user_grant(用户授权)两类,二者在配置声明、申请流程和用户交互方面存在显著差异。错误配置或流程缺失会导致功能异常、运行崩溃或上架失败。
• 痛点总结:
o 配置混淆:混淆两类权限的配置要求,用户权限缺少 reason/usedScene 导致审核不通过
o 检查缺失:调用敏感 API 前未验证权限状态,引发安全异常
o 申请遗漏:用户权限未触发动态申请弹窗,导致权限永久缺失
o 体验断层:用户拒绝后无引导措施,功能不可用且无反馈
• 技术总结:
o 配置规范:system_grant 仅需 name,user_grant 必须完整填写 reason 和 usedScene
o 检查工具:封装 `checkPermissions` 函数统一处理权限状态查询
o 申请流程:通过 `requestPermissionsFromUser` 触发系统授权弹窗,异步处理结果
o 降级策略:用户拒绝时提供日志提示,并引导至系统设置手动授权
• 避坑建议:
o 权限声明必填项:开发阶段对照官方文档确认每个权限的分类
o 调用前必检查:任何涉及 user_grant 权限的 API 调用前,务必先调用 `checkPermissions`
o 申请失败有引导:用户拒绝后,在 UI 层面给出友好提示,并提供跳转系统设置的能力
o 复用工具函数:将权限检查与申请逻辑封装为通用模块,避免重复代码
o 关注 SDK 版本:不同 API 版本对权限的处理可能有差异,建议使用稳定 API
- 点赞
- 收藏
- 关注作者
评论(0)