在Android应用开发中常用到权限请求,存在请求被拒绝后二次权限请求的场景。鸿蒙应用开发也有一样的场景。对于鸿蒙的权限类型,可以看看官网developer.huawei.com/consumer/cn… 尤其是受限开放权限的限制以及申请方式,也可以考虑代替方案。
本文将描述在Harmony应用中对位置权限的申请。
- 先看看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,这点在用户体验上非常好,不用用户自己手动去设置页找来找去了。
-
创建一个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代码截图如下
2)声明好了,在代码里面开始向用户申请授权。ArkTS代码,整两个工具类吧,一个位置的OHLocation.ets,一个权限的OHPermission.ets,src/main/ets/common目录下New->ArkTS File.
先写权限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()
首次
二次申请
这里位置相关的三个权限中后台运行时获取设备位置信息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);