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 下一步行动

  1. 实践练习:在现有项目中添加相机、位置等敏感权限的申请逻辑
  2. 代码优化:将权限管理代码封装成工具类,方便复用
  3. 测试验证:测试权限被拒绝、被撤销等边界场景的处理逻辑
  4. 文档查阅:查阅官方文档了解更多权限类型和特殊场景处理

9.3 常见问题

Q:权限申请弹窗不显示怎么办?

A:检查module.json5中权限声明是否正确,确认reason字段已填写,且权限类型为user_grant

Q:权限被永久拒绝后如何再次申请?

A:使用requestPermissionOnSetting方法引导用户到设置页手动开启,或调用openAppSettings打开应用设置页

Q:如何测试权限拒绝场景?

A:在系统设置中手动关闭应用权限,验证应用的降级处理逻辑是否正常

通过本篇文章的学习,你已经掌握了HarmonyOS权限管理的核心知识和实践技能。记住:权限管理不仅是技术问题,更是用户体验和隐私保护的重要环节。在实际开发中,始终遵循"最小权限原则"和"用户可控原则",让用户对应用的信任从权限管理开始。

posted @ 2025-12-23 23:18  J_____P  阅读(0)  评论(0)    收藏  举报