Harmony学习之权限申请与管理
Harmony学习之权限申请与管理
一、场景引入
小明正在开发一个拍照应用,需要访问设备的相机和麦克风。他发现直接调用相机API会报错,系统提示"权限不足"。原来在HarmonyOS中,访问敏感资源需要先获得用户授权。这让他意识到权限管理是保障用户隐私安全的重要机制。
二、核心概念
2.1 权限分类体系
HarmonyOS将权限分为三大类别,不同类别对应不同的授权方式和安全级别:
| 权限类型 | 授权方式 | 用户交互 | 示例 |
|---|---|---|---|
| 普通权限 | 安装时自动授予 | 无需用户操作 | 网络访问、蓝牙发现 |
| 敏感权限 | 运行时动态申请 | 需要用户明确授权 | 相机、位置、麦克风 |
| 系统权限 | 特殊申请流程 | 严格审核授权 | 系统设置、特殊功能 |
2.2 权限等级与APL
权限还分为三个APL等级:normal、system_basic、system_core。应用只能申请与其APL等级相匹配的权限,确保"最小权限原则"。
三、权限声明配置
3.1 配置文件声明
在module.json5文件中声明应用需要的权限,这是权限申请的第一步:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "用于拍照和视频通话功能",
"usedScene": {
"abilities": ["MainAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.MICROPHONE",
"reason": "用于录制音频",
"usedScene": {
"abilities": ["MainAbility"],
"when": "inuse"
}
}
]
}
}
关键参数说明:
name:权限名称,必须使用系统定义的权限常量reason:权限使用原因,用于向用户说明权限用途(user_grant权限必填)usedScene:权限使用场景,包括能力名称和调用时机(inuse/always)
四、动态权限申请
4.1 权限检查
在调用敏感操作前,需要先检查当前是否已获得授权:
// entry/src/main/ets/common/utils/PermissionUtil.ts
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import bundleManager from '@ohos.bundle.bundleManager';
import { BusinessError } from '@ohos.base';
export class PermissionUtil {
// 检查权限状态
static async checkPermission(permission: string): Promise<boolean> {
try {
const atManager = abilityAccessCtrl.createAtManager();
const tokenId = await this.getTokenId();
if (tokenId === -1) return false;
const grantStatus = await atManager.checkAccessToken(tokenId, permission);
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
} catch (error) {
console.error('检查权限失败:', error);
return false;
}
}
// 获取应用TokenID
private static async getTokenId(): Promise<number> {
try {
const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
);
return bundleInfo.appInfo.accessTokenId;
} catch (error) {
console.error('获取TokenID失败:', error);
return -1;
}
}
}
4.2 权限申请
当权限未授予时,需要向用户发起申请:
// entry/src/main/ets/common/utils/PermissionUtil.ts
import common from '@ohos.app.ability.common';
export class PermissionUtil {
// 动态申请权限
static async requestPermission(
context: common.UIAbilityContext,
permissions: string[]
): Promise<boolean> {
try {
const atManager = abilityAccessCtrl.createAtManager();
const result = await atManager.requestPermissionsFromUser(context, permissions);
// 检查所有权限是否都授予成功
const allGranted = result.authResults.every(
status => status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
);
return allGranted;
} catch (error) {
console.error('权限申请失败:', error);
return false;
}
}
}
4.3 权限使用场景
在具体功能中使用权限:
// entry/src/main/ets/pages/Index.ets
import { PermissionUtil } from '../common/utils/PermissionUtil';
@Entry
@Component
struct Index {
@State message: string = '点击拍照';
private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
// 拍照按钮点击事件
async onTakePhoto() {
// 检查相机权限
const hasPermission = await PermissionUtil.checkPermission('ohos.permission.CAMERA');
if (!hasPermission) {
// 申请权限
const granted = await PermissionUtil.requestPermission(
this.context,
['ohos.permission.CAMERA']
);
if (!granted) {
this.showPermissionTip();
return;
}
}
// 执行拍照操作
this.takePhoto();
}
// 拍照功能
private takePhoto() {
// 调用相机API进行拍照
console.log('开始拍照...');
// ... 相机调用代码
}
// 权限被拒绝提示
private showPermissionTip() {
promptAction.showToast({
message: '需要相机权限才能拍照,请前往设置开启',
duration: 3000
});
}
build() {
Column() {
Button(this.message)
.onClick(() => this.onTakePhoto())
}
}
}
五、权限拒绝处理
5.1 引导用户开启权限
当用户拒绝权限后,可以引导用户到设置页手动开启:
// entry/src/main/ets/common/utils/PermissionUtil.ts
export class PermissionUtil {
// 打开应用权限设置页
static openAppSettings(context: common.UIAbilityContext) {
const want = {
bundleName: 'com.huawei.hmos.settings',
abilityName: 'com.huawei.hmos.settings.MainAbility',
uri: 'application_info_entry',
parameters: {
pushParams: context.abilityInfo.bundleName
}
};
context.startAbility(want).catch((error: BusinessError) => {
console.error('打开设置页失败:', error);
});
}
// 用户拒绝后的二次申请
static async requestPermissionOnSetting(
context: common.UIAbilityContext,
permissions: string[]
): Promise<boolean> {
try {
const atManager = abilityAccessCtrl.createAtManager();
const grantStatus = await atManager.requestPermissionOnSetting(context, permissions);
return grantStatus.every(
status => status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
);
} catch (error) {
console.error('二次权限申请失败:', error);
return false;
}
}
}
5.2 优雅降级处理
权限被拒绝时提供替代方案:
// entry/src/main/ets/pages/Index.ets
@Entry
@Component
struct Index {
@State showPermissionDialog: boolean = false;
// 权限被拒绝后的处理
private async handlePermissionDenied() {
this.showPermissionDialog = true;
}
// 引导用户去设置
private goToSettings() {
PermissionUtil.openAppSettings(this.context);
this.showPermissionDialog = false;
}
// 使用替代方案
private useAlternative() {
// 例如:从相册选择图片代替拍照
console.log('使用相册选择图片');
this.showPermissionDialog = false;
}
build() {
Column() {
// ... 其他组件
if (this.showPermissionDialog) {
CustomDialog({
title: '权限提示',
message: '需要相机权限才能拍照,是否前往设置开启?',
confirm: {
value: '去设置',
action: () => this.goToSettings()
},
cancel: {
value: '取消',
action: () => this.useAlternative()
}
})
}
}
}
}
六、批量权限申请
对于需要同时申请多个权限的场景,可以使用批量申请方式:
// 位置权限组批量申请
async requestLocationPermissions() {
const permissions = [
'ohos.permission.APPROXIMATELY_LOCATION',
'ohos.permission.LOCATION'
];
const granted = await PermissionUtil.requestPermission(this.context, permissions);
if (granted) {
// 获取精确定位
this.getPreciseLocation();
} else {
// 降级使用模糊定位
this.getApproximateLocation();
}
}
七、最佳实践
7.1 按需申请原则
在用户真正需要使用功能时再申请权限,避免应用启动时一次性申请所有权限。
// 错误做法:应用启动时申请所有权限
aboutToAppear() {
this.requestAllPermissions(); // ❌ 不推荐
}
// 正确做法:按功能模块申请
async onOpenCamera() {
if (!await PermissionUtil.checkPermission('ohos.permission.CAMERA')) {
await this.requestCameraPermission(); // ✅ 推荐
}
this.openCamera();
}
7.2 最小权限原则
只申请应用功能真正需要的权限,避免过度申请:
// 错误做法:过度申请权限
const permissions = [
'ohos.permission.CAMERA',
'ohos.permission.LOCATION',
'ohos.permission.MICROPHONE'
]; // ❌ 拍照功能不需要位置和麦克风
// 正确做法:只申请必要权限
const permissions = ['ohos.permission.CAMERA']; // ✅ 只申请相机权限
7.3 渐进式授权
按功能模块逐步申请权限,让用户理解每个权限的用途:
// 拍照功能申请相机权限
async onTakePhoto() {
await this.requestCameraPermission();
}
// 录音功能申请麦克风权限
async onRecordAudio() {
await this.requestMicrophonePermission();
}
7.4 多语言适配
reason字段需要做多语言适配,确保不同语言用户都能理解权限用途:
// entry/src/main/resources/base/string.json
{
"camera_permission_reason": "用于拍照和视频通话功能",
"microphone_permission_reason": "用于录制音频"
}
八、实战案例:相机应用权限管理
8.1 完整权限管理封装
// entry/src/main/ets/common/utils/CameraPermissionManager.ts
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import bundleManager from '@ohos.bundle.bundleManager';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';
export class CameraPermissionManager {
private context: common.UIAbilityContext;
private atManager: abilityAccessCtrl.AtManager;
constructor(context: common.UIAbilityContext) {
this.context = context;
this.atManager = abilityAccessCtrl.createAtManager();
}
// 检查相机权限
async checkCameraPermission(): Promise<boolean> {
try {
const tokenId = await this.getTokenId();
const grantStatus = await this.atManager.checkAccessToken(
tokenId,
'ohos.permission.CAMERA'
);
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
} catch (error) {
console.error('检查相机权限失败:', error);
return false;
}
}
// 申请相机权限
async requestCameraPermission(): Promise<boolean> {
try {
const result = await this.atManager.requestPermissionsFromUser(
this.context,
['ohos.permission.CAMERA']
);
return result.authResults.every(
status => status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
);
} catch (error) {
console.error('申请相机权限失败:', error);
return false;
}
}
// 获取TokenID
private async getTokenId(): Promise<number> {
try {
const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
);
return bundleInfo.appInfo.accessTokenId;
} catch (error) {
console.error('获取TokenID失败:', error);
return -1;
}
}
}
8.2 页面中使用
// entry/src/main/ets/pages/CameraPage.ets
import { CameraPermissionManager } from '../common/utils/CameraPermissionManager';
@Entry
@Component
struct CameraPage {
private permissionManager: CameraPermissionManager;
@State hasPermission: boolean = false;
aboutToAppear() {
this.permissionManager = new CameraPermissionManager(
getContext(this) as common.UIAbilityContext
);
this.checkPermission();
}
// 检查权限状态
async checkPermission() {
this.hasPermission = await this.permissionManager.checkCameraPermission();
}
// 申请权限
async requestPermission() {
const granted = await this.permissionManager.requestCameraPermission();
this.hasPermission = granted;
if (!granted) {
promptAction.showToast({
message: '需要相机权限才能使用拍照功能',
duration: 3000
});
}
}
build() {
Column() {
if (this.hasPermission) {
// 相机预览组件
CameraPreview()
} else {
Button('开启相机权限')
.onClick(() => this.requestPermission())
}
}
}
}
九、总结与行动建议
9.1 核心要点回顾
- 权限分类:掌握普通权限、敏感权限、系统权限的区别和授权方式
- 声明配置:在module.json5中正确声明权限,提供清晰的reason和usedScene
- 动态申请:遵循"检查->申请->处理结果"的标准流程
- 拒绝处理:权限被拒绝时提供友好的引导和替代方案
9.2 下一步行动
- 实践练习:在现有项目中添加相机、位置等敏感权限的申请逻辑
- 代码优化:将权限管理代码封装成工具类,方便复用
- 测试验证:测试权限被拒绝、被撤销等边界场景的处理逻辑
- 文档查阅:查阅官方文档了解更多权限类型和特殊场景处理
9.3 常见问题
Q:权限申请弹窗不显示怎么办?
A:检查module.json5中权限声明是否正确,确认reason字段已填写,且权限类型为user_grant
Q:权限被永久拒绝后如何再次申请?
A:使用requestPermissionOnSetting方法引导用户到设置页手动开启,或调用openAppSettings打开应用设置页
Q:如何测试权限拒绝场景?
A:在系统设置中手动关闭应用权限,验证应用的降级处理逻辑是否正常
通过本篇文章的学习,你已经掌握了HarmonyOS权限管理的核心知识和实践技能。记住:权限管理不仅是技术问题,更是用户体验和隐私保护的重要环节。在实际开发中,始终遵循"最小权限原则"和"用户可控原则",让用户对应用的信任从权限管理开始。

浙公网安备 33010602011771号