实操鸿蒙应用开发中权限请求的使用(HarmonySDK API12 requestPermissionOnSetting)

0 阅读4分钟

在Android应用开发中常用到权限请求,存在请求被拒绝后二次权限请求的场景。鸿蒙应用开发也有一样的场景。对于鸿蒙的权限类型,可以看看官网developer.huawei.com/consumer/cn… 尤其是受限开放权限的限制以及申请方式,也可以考虑代替方案。

本文将描述在Harmony应用中对位置权限的申请。

  1. 先看看HarmonyOS NEXT Release指南,
  • 关于应用权限的部分,developer.huawei.com/consumer/cn… 我们要申请的位置相关的权限是一组共三个权限,分别为

    1)ohos.permission.LOCATION_IN_BACKGROUND:应用在后台运行时获取设备位置信息

    2)ohos.permission.APPROXIMATELY_LOCATION:申请前台模糊位置权限

    3)ohos.permission.LOCATIO:申请前台精确位置权限(需要和2一起申请)

  • 如何申请权限,developer.huawei.com/consumer/cn… 包括了声明权限、向用户申请授权、二次向申请授权。其中api12中提供了二次向申请授权的方法requestPermissionOnSetting,这点在用户体验上非常好,不用用户自己手动去设置页找来找去了。

  1. 创建一个Harmony工程命名PermissionDemo。

    1)按照指南要求先声明权限,在src/entry/module.json5文件中添加requestPermissions三个位置相关的权限name,这里也要注意因为这三个权限都是user_grant级别的权限,所以'reason'和'usedScene'都是必填项,不然会报错,也有提示The 'reason' and 'usedScene' attributes are mandatory for user_grant permissions.其中reason必须要使用string类资源引用,src/main/resources/base/element/string.json中,string.json和module.json5代码截图如下

image.png

image.png

2)声明好了,在代码里面开始向用户申请授权。ArkTS代码,整两个工具类吧,一个位置的OHLocation.ets,一个权限的OHPermission.ets,src/main/ets/common目录下New->ArkTS File.

image.png

先写权限OHPermission.ets,三步走:检查权限checkPermissionsArray、首次申请reqPermissionsArrayFromUser、二次申请requestPermissionsArrayOnSetting。 这里检查权限checkPermissionsArray,我们要检查一组多个权限,鸿蒙sdk提供的只有一个权限的参数检查方法checkAccessTokenSync. 将首次申请和二次申请的方法封装在requestPermissionsArray对外提供。 对于检查权限和申请权限的结果封装为export interface PermissionGrantStatusResult 完整代码

import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import Logger from './Logger';
let TAG = "OHPermission"
const context = getContext(this) as common.UIAbilityContext;
export interface PermissionGrantStatusResult{
  permissionsAll:Permissions[];
  permissionsDenied:Permissions[];//被拒绝
  grantStatus:boolean;
}

export default class OHPermission {
  //-1 PERMISSION_DENIED表示未授权。0 PERMISSION_GRANTED表示已授权。
  static async checkPermissionsArray(permissionsArray: Array<Permissions>): Promise<PermissionGrantStatusResult> {
    //Logger.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 = OHPermission.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
  }

  /**
   * 动态申请权限
   * @param permissions
   * @returns 相应请求权限的结果。
   */
  static async requestPermissionsArray(permissionArray: Array<Permissions>): Promise<PermissionGrantStatusResult>{
    Logger.debug(TAG,'requestPermissionsArray permissionArray:' + JSON.stringify(permissionArray));
    let result =  await OHPermission.reqPermissionsArrayFromUser(permissionArray)
    if (!result.grantStatus) {
      result = await OHPermission.requestPermissionsArrayOnSetting(result.permissionsDenied)
    }
    Logger.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);
      Logger.debug(TAG, `checkPermissionGrant 检查${permission},grantStatus=${grantStatus},权限状态为:${grantStatus ===
      abilityAccessCtrl.GrantStatus.PERMISSION_DENIED ? "未授权" : "已授权"}`);
    } catch (error) {
      const err = error as BusinessError;
      Logger.debug(TAG, `checkPermissionGrant检查${permission}权限异常:${JSON.stringify(err)}`);
      grantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
    }
    return grantStatus;
  }

  /**
   * 动态申请权限 首次请求
   * @param permissions
   * @returns 相应请求权限的结果。
   */
  private static async reqPermissionsArrayFromUser(permissionsArray: Array<Permissions>): Promise<PermissionGrantStatusResult> {
    Logger.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) => {
      Logger.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 = "请求无效"
        }
        Logger.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) => {
      Logger.debug(TAG, `reqPermissionArrayFromUser err:${err?.message},${JSON.stringify(permissionsArray)}`);
      let result = {
        "permissionsAll": permissionsArray,
        "permissionsDenied": permissionsArray,
        "grantStatus": false
      } as PermissionGrantStatusResult
      return result
    });
  }

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

OHLocation.ets中我用使用的是鸿蒙的@kit.LocationKit,因为我用的模拟器是x86,三方百度定位的sdk不支持x86.完整代码如下:

import OHPermission from './OHPermission';
import { Permissions } from '@kit.AbilityKit';
import { geoLocationManager } from '@kit.LocationKit';
import Logger from './Logger';
import { BusinessError } from '@kit.BasicServicesKit';

const TAG = "OHLocation"
export default class OHLocation {
  async getCurrentLocationWithPermission():Promise<GeoLocationInfo>{
    let locationPermissions: Array<Permissions> = ['ohos.permission.LOCATION','ohos.permission.APPROXIMATELY_LOCATION']
    // 校验是否被授予定位权限
    return OHPermission.checkPermissionsArray(locationPermissions).then((data)=>{
      if (!data.grantStatus) {
        return OHPermission.requestPermissionsArray(data.permissionsDenied).then((r)=>{
          if (r.grantStatus) {
            return this.getCurrentLocation()
          }else {
            throw Error("获取权限失败")
          }
        })
      }else {
        return this.getCurrentLocation()
      }
    }).catch((error:Error)=>{
      let geoLocationInfo = {"status":false,"msg":error.message} as GeoLocationInfo
      return geoLocationInfo
    })
  }

  async getCurrentLocation():Promise<GeoLocationInfo>{
    let request: geoLocationManager.SingleLocationRequest = {
      'locatingPriority': geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
      'locatingTimeoutMs': 10000
    }
    return geoLocationManager.getCurrentLocation(request).then((result) => { // 调用getCurrentLocation获取当前设备位置,通过promise接收上报的位置
      Logger.debug(TAG,"getCurrentLocation======= location="+JSON.stringify(result))
      let location:geoLocationManager.Location = result
      let geoLocationInfo = {"status":true,lng:location.longitude,lat:location.latitude} as GeoLocationInfo
      return geoLocationInfo
    }).catch((error:BusinessError) => { // 接收上报的错误码
      Logger.debug(TAG,"getCurrentLocation======= location="+JSON.stringify(error))
      let geoLocationInfo = {"status":false} as GeoLocationInfo
      return geoLocationInfo
    });
  }
}
export interface GeoLocationInfo{
  address:string;
  lng:number;
  lat:number;
  status:boolean;
  msg:string
}

在src/main/ets/pages/Index.ets中调用new OHLocation().getCurrentLocationWithPermission()

首次

image.png

二次申请

image.png

这里位置相关的三个权限中后台运行时获取设备位置信息ohos.permission.LOCATION_IN_BACKGROUND,不能向用户申请,官方有说明developer.huawei.com/consumer/cn…

具体现象是,当使用sdk的 requestPermissionsFromUser 同时请求三个权限时,返回的结果是 data.authResults=[2,2,2](值0表示授权,值-1表示不授权,值2表示请求无效)

代码中Log工具

import hilog from '@ohos.hilog';

const LOGGER_PREFIX: string = 'next_demo';
class Logger {
  private domain: number;
  private prefix: string;
  private format: string = '%{public}s:%{public}s';
  constructor(prefix: string = '', domain: number = 0xFF00) {
    this.prefix = prefix;
    this.domain = domain;
  }

  debug(tag:string,args?: string): void {
    hilog.debug(this.domain, this.prefix, this.format, tag,args);
  }

  info(tag:string,args?: string): void {
    hilog.info(this.domain, this.prefix, this.format, args);
  }

  warn(...args: string[]): void {
    hilog.warn(this.domain, this.prefix, this.format, args);
  }

  error(...args: string[]): void {
    hilog.error(this.domain, this.prefix, this.format, args);
  }
}
export default new Logger(LOGGER_PREFIX);