HarmonyOS鸿蒙Next中如何优雅处理权限请求?

HarmonyOS鸿蒙Next中如何优雅处理权限请求? 问题描述

在HarmonyOS开发中,权限管理是应用安全的重要组成部分。在应用中,需要处理各种权限请求,如位置权限、存储权限等。

3 回复

自定义一个PermissionHelper权限管理工具类,封装权限检查和请求的逻辑,提供批量权限校验、动态权限申请等功能,简化权限管理的复杂度,

代码示例:

import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { LogUtils } from   './LogUtils';
let TAG = "PermissionHelper"
const context = getContext(this) as common.UIAbilityContext;
export interface PermissionGrantStatusResult{
  permissionsAll:Permissions[];
  permissionsDenied:Permissions[];//被拒绝
  grantStatus:boolean;
}
/**
 * @author J.query
 * @date 2024/12/5 10:34
 * @email j-query@foxmail.com
 * Description:
 */
export default class PermissionHelper {
  //
  /**
   * 批量校验权限
   * -1 PERMISSION_DENIED表示未授权。0 PERMISSION_GRANTED表示已授权。1 PERMISSION_GRANTED_BACKGROUND_LIMITED表示临时授权。
   * @param permissionsArray
   * @returns
   */
  static async checkPermissionsArray(permissionsArray: Array<Permissions>): Promise<PermissionGrantStatusResult> {
    //LogUtils.debug(TAG,'checkPermissionsArray permissionArray:' + JSON.stringify(permissionsArray));
    let authResults = new Array<number>(permissionsArray.length)
    let permissionsDenied: Array<Permissions> = new Array()
    permissionsArray.forEach((value, index) => {
      const grantStatus: abilityAccessCtrl.GrantStatus = PermissionHelper.checkPermissionGrant(value);
      authResults[index] = grantStatus;
      if (grantStatus != abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
        permissionsDenied.push(permissionsArray[index])
      }
    })
    let result = {
      "permissionsAll": permissionsArray,
      "permissionsDenied": permissionsDenied,
      "grantStatus": permissionsDenied.length <= 0
    } as PermissionGrantStatusResult
    return result
  }

  /**
   * 动态申请权限 二次请求 api12中提供的
   * @param permissionArray
   * @returns  相应请求权限的结果。
   */
  static async requestPermissionsArray(permissionArray: Array<Permissions>): Promise<PermissionGrantStatusResult>{
    LogUtils.debug(TAG,'requestPermissionsArray permissionArray:' + JSON.stringify(permissionArray));
    let result =  await PermissionHelper.reqPermissionsArrayFromUser(permissionArray)
    if (!result.grantStatus) {
      result = await PermissionHelper.requestPermissionsArrayOnSetting(result.permissionsDenied)
    }
    LogUtils.debug(TAG,'requestPermissionsArray permissionArray:' + JSON.stringify(result));
    return result
  }

  /**
   * 校验应用是否被授予 {permission} 权限
   * @param permission
   * @returns
   */
  private static checkPermissionGrant(permission: Permissions): abilityAccessCtrl.GrantStatus {
    const atManager = abilityAccessCtrl.createAtManager();
    // 初始化grantStatus为未授权
    let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
    let tokenId: number = 0;
    try {
      // 获取应用程序的accessTokenID
      const bundleInfo: bundleManager.BundleInfo =
        bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
      const appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
      tokenId = appInfo.accessTokenId;
      // 校验应用是否被授予权限
      grantStatus = atManager.checkAccessTokenSync(tokenId, permission);
      LogUtils.debug(TAG, `checkPermissionGrant 检查${permission},grantStatus=${grantStatus},权限状态为:${grantStatus ===
      abilityAccessCtrl.GrantStatus.PERMISSION_DENIED ? "未授权" : "已授权"}`);
    } catch (error) {
      const err = error as BusinessError;
      LogUtils.debug(TAG, `checkPermissionGrant检查${permission}权限异常:${JSON.stringify(err)}`);
      grantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
    }
    return grantStatus;
  }


  /**
   * 动态申请权限 首次请求
   * @param permissionsArray
   * @returns
   */
  private static async reqPermissionsArrayFromUser(permissionsArray: Array<Permissions>): Promise<PermissionGrantStatusResult> {
    LogUtils.debug(TAG, 'reqPermissionArrayFromUser permissionArray:' + JSON.stringify(permissionsArray));
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    let permissionsDenied: Array<Permissions> = new Array()
    return atManager.requestPermissionsFromUser(context, permissionsArray).then((data) => {
      LogUtils.debug(TAG, 'requestPermissionsFromUser:' + JSON.stringify(data));
      let grantStatus: Array<number> = data.authResults;
      let permissions: Array<string> = data.permissions;
      let dialogShownResults: Array<boolean> | undefined = data.dialogShownResults
      permissions.forEach((value, index) => {
        let dialogShown = false
        let grantStatusTxt = "未知"
        if (dialogShownResults != undefined) {
          dialogShown = dialogShownResults[index]
        }
        //值0表示授权,值-1表示不授权,值2表示请求无效。
        if (grantStatus[index] == 0) {
          grantStatusTxt = "授权"
        } else if (grantStatus[index] == -1) {
          grantStatusTxt = "不授权"
        } else if (grantStatus[index] == 2) {
          grantStatusTxt = "请求无效"
        }
        LogUtils.debug(TAG,
          `requestPermissionsFromUser [${value},${grantStatusTxt},${dialogShown ? "已经弹窗" : "没有弹窗"}]`);
        if (grantStatus[index] === -1 && !dialogShown) {
          permissionsDenied.push(permissionsArray[index])
        } else if (grantStatus[index] === 2) {
          throw new Error("请求无效")
        }
      })
      let result = {
        "permissionsAll": permissionsArray,
        "permissionsDenied": permissionsDenied,
        "grantStatus": permissionsDenied.length <= 0
      } as PermissionGrantStatusResult
      return result
    }).catch((err: BusinessError) => {
      LogUtils.debug(TAG, `reqPermissionArrayFromUser err:${err?.message},${JSON.stringify(permissionsArray)}`);
      let result = {
        "permissionsAll": permissionsArray,
        "permissionsDenied": permissionsArray,
        "grantStatus": false
      } as PermissionGrantStatusResult
      return result
    });
  }

  /**
   * 动态申请权限 二次请求 api12中提供
   * @param permissionsArray
   * @returns 相应请求权限的结果。值0表示授权,值-1表示不授权
   */
  private static async requestPermissionsArrayOnSetting(permissionsArray: Array<Permissions>): Promise<PermissionGrantStatusResult>{
    let permissionsDenied: Array<Permissions> = new Array()
    LogUtils.debug(TAG,'requestPermissionOnSetting permissionArray:' + JSON.stringify(permissionsArray));
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    return atManager.requestPermissionOnSetting(context, permissionsArray).then((data: Array<abilityAccessCtrl.GrantStatus>) => {//api12中提供
      LogUtils.debug(TAG,'requestPermissionOnSetting data:' + JSON.stringify(data));
      permissionsArray.forEach((value,index)=>{
        LogUtils.debug(TAG,`requestPermissionOnSetting [${value},${data[index] === abilityAccessCtrl.GrantStatus.PERMISSION_DENIED ? "用户拒绝授权" : "用户授权"}]` );
        if (data[index]===abilityAccessCtrl.GrantStatus.PERMISSION_DENIED) {
          permissionsDenied.push(permissionsArray[index])
        }
      })
      let result = {"permissionsAll":permissionsArray,
        "permissionsDenied" :permissionsDenied,
        "grantStatus":permissionsDenied.length<=0} as PermissionGrantStatusResult
      return result
    }).catch((err: BusinessError) => {
      LogUtils.debug(TAG,`reqPermissionArrayFromUser err:${err?.message},${JSON.stringify(permissionsArray)}`);
      let result = {"permissionsAll":permissionsArray,
        "permissionsDenied" :permissionsArray,
        "grantStatus":false} as PermissionGrantStatusResult
      return result
    });
  }
}

详细回答

Q1: PermissionHelper.ets是什么?

A1: PermissionHelper.ets是HarmonyOS应用中的权限管理工具类,主要封装了权限检查和请求的逻辑,提供批量权限校验、动态权限申请等功能,简化了权限管理的复杂度。

Q2: PermissionHelper.ets的主要功能是什么?

A2:

  • 批量校验权限: checkPermissionsArray - 检查多个权限的授权状态
  • 动态申请权限: requestPermissionsArray - 请求权限,支持二次请求
  • 权限状态检查: checkPermissionGrant - 检查单个权限是否被授予

Q3: 如何使用PermissionHelper进行权限检查?

A3:

import { Permissions } from '@kit.AbilityKit';
import PermissionHelper from './PermissionHelper';

// 示例1:检查位置权限
async function checkLocationPermission() {
  const permissionsArray: Permissions[] = [
    'ohos.permission.APPROXIMATELY_LOCATION' ,
    'ohos.permission.LOCATION'
  ];
  
  try {
    const result = await PermissionHelper.checkPermissionsArray(permissionsArray);
    
    if (result.grantStatus) {
      console.log('位置权限已获得');
      // 执行需要位置权限的操作
    } else {
      console.log('位置权限未获得');
      // 请求权限
      await requestLocationPermission();
    }
  } catch (error) {
    console.error('检查位置权限时出错:', error);
  }
}

// 示例2:请求位置权限
async function requestLocationPermission() {
   const permissionsArray: Permissions[] = [
    'ohos.permission.APPROXIMATELY_LOCATION' ,
    'ohos.permission.LOCATION'
  ];

  
  try {
    const result = await PermissionHelper.requestPermissionsArray(permissionsArray);
    
    if (result.grantStatus) {
      console.log('位置权限请求成功');
      // 执行需要位置权限的操作
    } else {
      console.log('位置权限请求失败');
      console.log('被拒绝的权限:', result.permissionsDenied);
    }
  } catch (error) {
    console.error('请求位置权限时出错:', error);
  }
}

// 示例3:批量检查多个权限
async function checkMultiplePermissions() {
  const permissionsArray: Permissions[] = [
    'ohos.permission.APPROXIMATELY_LOCATION',
    'ohos.permission.LOCATION' ,
    'ohos.permission.STORAGE_MANAGER'
  ];
  
  try {
    const result = await PermissionHelper.checkPermissionsArray(permissionsArray);
    
    console.log('所有权限:', result.permissionsAll);
    console.log('被拒绝的权限:', result.permissionsDenied);
    console.log('是否全部授权:', result.grantStatus);
    
    if (!result.grantStatus) {
      // 请求被拒绝的权限
      const requestResult = await PermissionHelper.requestPermissionsArray(result.permissionsDenied);
      console.log('权限请求结果:', requestResult);
    }
  } catch (error) {
    console.error('处理权限时出错:', error);
  }
}

// 示例4:位置权限使用示例
async function requestLocationPermissions() {
  // 使用正确的权限类型
  const locationPermissions: Permissions[] = [
    'ohos.permission.APPROXIMATELY_LOCATION',
    'ohos.permission.LOCATION'
  ];
  
  try {
    const result = await PermissionHelper.checkPermissionsArray(locationPermissions);
    
    console.log('位置权限检查结果:', result);
    
    if (!result.grantStatus) {
      console.log('请求位置权限...');
      const requestResult = await PermissionHelper.requestPermissionsArray(locationPermissions);
      console.log('位置权限请求结果:', requestResult);
    } else {
      console.log('位置权限已获得');
    }
  } catch (error) {
    console.error('处理位置权限时出错:', error);
  }
}

export {
  checkLocationPermission,
  requestLocationPermission,
  checkMultiplePermissions,
  requestLocationPermissions
};

效果

使用PermissionHelper.ets的优势

  1. 统一的权限管理: 所有权限请求都通过统一的接口处理,便于维护
  2. 批量处理能力: 支持一次性检查或请求多个权限,提高效率
  3. 自动重试机制: 对于被拒绝的权限,提供二次请求机会
  4. 详细的权限状态: 提供具体的被拒绝权限列表,便于针对性处理
  5. 错误处理完善: 包含完整的异常处理逻辑,提高应用稳定性

实际应用场景

在HarmonyOS应用中,使用PermissionHelper可以:

  • 在获取位置信息前检查必要的权限
  • 当权限不足时引导用户授权
  • 提供良好的用户体验,避免因权限问题导致的功能异常
  • 符合HarmonyOS的安全规范和用户体验标准

更多关于HarmonyOS鸿蒙Next中如何优雅处理权限请求?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中,通过@ohos.abilityAccessCtrl模块处理权限请求。使用requestPermissionsFromUser方法在运行时申请权限,并通过onRequestPermissionsFromUserResult回调处理授权结果。权限需在module.json5文件中声明。

在HarmonyOS Next中,权限请求的优雅处理应遵循“最小化、透明化、用户友好”的原则。核心是通过权限管理模块(securityManager)和声明式UI(ArkTS)实现。

1. 权限声明与检查

  • module.json5中精确声明所需权限,仅申请业务必需的权限。
  • 使用abilityAccessCtrlverifyAccessTokenSync()方法,在权限使用前同步检查授权状态,避免无效请求。

2. 动态请求与用户引导

  • 对于敏感权限(如位置、相机),必须使用动态申请。通过requestPermissionsFromUser()异步请求,并妥善处理回调。
  • 在请求前,通过弹窗或界面文字向用户清晰说明权限用途(遵循“权限使用目的”规范),提升通过率。

3. 声明式UI与状态管理

  • 利用ArkUI的声明式特性和状态管理(@State@Prop),将权限状态与UI组件绑定。例如,权限未授予时禁用相关按钮,授予后自动更新界面。
  • 示例代码结构:
    [@Entry](/user/Entry)
    [@Component](/user/Component)
    struct MyComponent {
      @State permissionGranted: boolean = false
    
      // 检查权限状态
      checkPermission() {
        // ... 调用verifyAccessTokenSync
        this.permissionGranted = ...
      }
    
      // 请求权限
      async requestPermission() {
        let result = await abilityAccessCtrl.requestPermissionsFromUser(...)
        this.permissionGranted = ...
      }
    
      build() {
        Column() {
          if (this.permissionGranted) {
            // 授权后的UI
            Text('已授权')
          } else {
            // 未授权时的UI与请求按钮
            Button('申请权限').onClick(() => this.requestPermission())
          }
        }
      }
    }
    

4. 处理拒绝与引导设置

  • 用户拒绝后,不应重复弹窗骚扰。可记录状态,并在功能入口再次提示,并提供跳转系统设置页的引导(使用startAbility跳转Settings.ACTION_MANAGE_APP_PERMISSIONS)。
  • 通过onPermissionRejected回调处理拒绝逻辑,解释权限必要性。

5. 后台权限与持续使用

  • 对于需要后台持续使用的权限(如后台定位),需单独申请ACTIVITY_CONTINUOUS类型权限,并在UI中明确告知用户后台使用场景。

总结:HarmonyOS Next的权限处理应融入声明式UI开发流,以状态驱动界面,在关键节点提供清晰用户引导,并尊重用户选择。

回到顶部