前言
系统提供了一种允许应用访问系统资源(如:通讯录等)和系统能力(如:访问摄像头、麦克风等)的通用权限访问方式,来保护系统数据(包括用户个人数据)或功能,避免它们被不当或恶意使用。
权限组和子权限
为了尽可能减少系统弹出的权限弹窗数量,优化交互体验,系统将逻辑紧密相关的user_grant权限组合在一起,形成多个权限组。
当应用请求权限时,同一个权限组的权限将会在一个弹窗内一起请求用户授权。权限组中的某个权限,称之为该权限组的子权限。
例如图片和视频读取权限和写入权限,可以放到一起合并申请,我们可以看到申请权限的 api 中权限的参数接收的是一个数组类型: requestPermissionsFromUser(context: Context, permissionList: Array<Permissions>, requestCallback: AsyncCallback<PermissionRequestResult>): void
具体哪些权限属于同个权限组,可以参考应用权限组列表
在配置文件中声明权限
在Entry 模块的module.json5 中配置"requestPermissions",配置应用中需要的权限类型,示例中配置了相机权限、图片和视频的读写权限,其中图片和视频的读和写可以合并为一个权限组,详见后面示例。
"requestPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "$string:permission_app_camera_reason",
"usedScene": {
"when": "inuse"
}
},
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"reason": "$string:permission_app_read_image_video_reason",
"usedScene": {
"when": "inuse"
}
},
{
"name": "ohos.permission.WRITE_IMAGEVIDEO",
"reason": "$string:permission_app_write_image_video_reason",
"usedScene": {
"when": "inuse"
}
}
]
检查权限是否已授权
在申请权限之前,先通过checkAccessToken()校验权限是否已授权。
/**
* 检查权限状态
* @param permission 权限
*/
static async checkPermissionGrantStatus(permission: Permissions): Promise<boolean> {
// 获取应用程序的accessTokenID。
let tokenId: number = 0;
try {
let bundleInfo: bundleManager.BundleInfo =
await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
tokenId = bundleInfo.appInfo.accessTokenId;
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
}
// 权限状态
let grantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
// 校验应用是否被授予权限。
try {
grantStatus = await abilityAccessCtrl.createAtManager().checkAccessToken(tokenId, permission);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
}
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}
向用户申请权限
向用户申请权限可以通过requestPermissionsFromUser()实现,系统不鼓励频繁弹窗打扰用户。如果用户拒绝授权,将无法再次弹窗。但我们可能有这么一个场景,需要在非首次请求用户权限获取到未授权状态时,引导用户到系统应用设置去进行开启权限,如何判断呢?requestPermissionsFromUser的回调中在 api12+增加了dialogShownResults属性,用于判断是否进行了弹窗,因此我们请求权限的方法除了回调授权状态,还回调了是否有弹窗。
/**
* 请求权限
* @param permissions 权限列表
*/
static requestPermissions(context: Context, permissions: Permissions[]): Promise<[boolean, boolean]> {
return new Promise<[boolean, boolean]>((resolve, reject) => {
abilityAccessCtrl.createAtManager().requestPermissionsFromUser(context, permissions)
.then((result) => {
let isGrant = true
result.authResults.forEach((authResult) => {
if (authResult !== 0) {
isGrant = false
}
})
let isDialogShown = false
if (result.dialogShownResults) {
isDialogShown = result.dialogShownResults.includes(true)
}
resolve([isGrant, isDialogShown])
})
.catch((err: BusinessError) => {
reject()
})
})
}
引导用户跳转系统应用设置
当请求用户授权首次弹窗后,无法再次弹窗,但用户未来可能希望更改授权状态,应用需引导用户在系统设置中手动授予权限,可以从应用中直接跳转到系统应用设置中。
/**
* 跳转系统应用设置
* @param context
*/
static async jumpToPermission(context: common.UIAbilityContext): Promise<void> {
let bundleInfo: bundleManager.BundleInfo =
await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
let want: Want = {
bundleName: 'com.huawei.hmos.settings',
abilityName: 'com.huawei.hmos.settings.MainAbility',
uri: 'application_info_entry',
parameters: {
pushParams: bundleInfo.name
}
};
context.startAbility(want).then(() => {
console.info('TAG', 'jump to permission success.');
}).catch((error: BusinessError) => {
console.error('TAG', `jump to permission failed. Code: ${error.code}, message is ${error.message}`);
});
}
完整代码
ObservedArray.ets
/**
* 观察数组
*/
export class ObservedArray<T> extends Array<T> {
constructor(...args: T[]) {
super(...args)
}
}
UserPermissionModel.ets
import { Permissions } from '@kit.AbilityKit'
import { UserPermissionHandle } from './UserPermissionHandle'
/**
* 用户权限模型
*/
@Observed
export class UserPermissionModel {
name: string
permissions: Permissions[]
isGrant: boolean = false
constructor(name: string, permission: Permissions[]) {
this.name = name
this.permissions = permission
}
// 检查权限
async checkPermissionGrantStatus() {
try {
let isGrant = true
for (let i = 0; i < this.permissions.length; i++) {
let permission = this.permissions[i]
let value = await UserPermissionHandle.checkPermissionGrantStatus(permission)
if (!value) {
isGrant = false // 其中一个未授权,权限组判定为未授权
}
}
this.isGrant = isGrant
} catch (err) {
this.isGrant = false
}
}
// 请求权限
async requestPermissions(context: Context) {
return await UserPermissionHandle.requestPermissions(context, this.permissions)
}
}
UserPermissionHandle.ets
import abilityAccessCtrl, { PermissionRequestResult, Permissions } from "@ohos.abilityAccessCtrl";
import bundleManager from "@ohos.bundle.bundleManager";
import { BusinessError } from "@kit.BasicServicesKit";
import { common, Context, Want } from "@kit.AbilityKit";
/**
* 用户权限处理类
*/
export class UserPermissionHandle {
/**
* 检查权限状态
* @param permission 权限
*/
static async checkPermissionGrantStatus(permission: Permissions): Promise<boolean> {
// 获取应用程序的accessTokenID。
let tokenId: number = 0;
try {
let bundleInfo: bundleManager.BundleInfo =
await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
tokenId = bundleInfo.appInfo.accessTokenId;
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
}
// 权限状态
let grantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
// 校验应用是否被授予权限。
try {
grantStatus = await abilityAccessCtrl.createAtManager().checkAccessToken(tokenId, permission);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
}
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}
/**
* 请求权限
* @param permissions 权限列表
*/
static requestPermissions(context: Context, permissions: Permissions[]): Promise<[boolean, boolean]> {
return new Promise<[boolean, boolean]>((resolve, reject) => {
abilityAccessCtrl.createAtManager().requestPermissionsFromUser(context, permissions)
.then((result) => {
let isGrant = true
result.authResults.forEach((authResult) => {
if (authResult !== 0) {
isGrant = false
}
})
let isDialogShown = false
if (result.dialogShownResults) {
isDialogShown = result.dialogShownResults.includes(true)
}
resolve([isGrant, isDialogShown])
})
.catch((err: BusinessError) => {
reject()
})
})
}
/**
* 跳转系统应用设置
* @param context
*/
static async jumpToPermission(context: common.UIAbilityContext): Promise<void> {
let bundleInfo: bundleManager.BundleInfo =
await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
let want: Want = {
bundleName: 'com.huawei.hmos.settings',
abilityName: 'com.huawei.hmos.settings.MainAbility',
uri: 'application_info_entry',
parameters: {
pushParams: bundleInfo.name
}
};
context.startAbility(want).then(() => {
console.info('TAG', 'jump to permission success.');
}).catch((error: BusinessError) => {
console.error('TAG', `jump to permission failed. Code: ${error.code}, message is ${error.message}`);
});
}
}
Index.ets
import { ObservedArray } from '../common/ObservedArray';
import { UserPermissionHandle } from '../common/UserPermissionHandle';
import { UserPermissionModel } from '../common/UserPermissionModel';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct Index {
@State permissionModels: ObservedArray<UserPermissionModel> = new ObservedArray<UserPermissionModel>(
new UserPermissionModel('相机权限', ['ohos.permission.CAMERA']),
new UserPermissionModel('相册权限', ['ohos.permission.READ_IMAGEVIDEO', 'ohos.permission.WRITE_IMAGEVIDEO']),
new UserPermissionModel('麦克风权限', ['ohos.permission.MICROPHONE']),
new UserPermissionModel('位置信息权限', ['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION']),
)
onPageShow(): void {
this.permissionModels.forEach((model) => {
model.checkPermissionGrantStatus()
})
}
build() {
Navigation() {
Column() {
List() {
ForEach(this.permissionModels, (model: UserPermissionModel) => {
ListItemView({
model: model,
onClickToggle: async () => {
let context = this.getUIContext().getHostContext() as common.UIAbilityContext
if (!context) {
return
}
if (!model.isGrant) {
let result = await UserPermissionHandle.requestPermissions(context, model.permissions)
let isGrant = result[0]
let isDialogShown = result[1]
model.isGrant = isGrant
if (!isGrant && !isDialogShown) {
// 未授权,且无弹窗,则跳转系统应用设置
UserPermissionHandle.jumpToPermission(context)
}
} else {
UserPermissionHandle.jumpToPermission(context)
}
}
})
.padding({
left: 20,
right: 20
})
})
}
.width('100%')
.layoutWeight(1)
.edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true })
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.title('权限管理')
}
}
@Component
struct ListItemView {
@ObjectLink model: UserPermissionModel
onClickToggle?: () => void
build() {
Row() {
Text(this.model.name)
Stack() {
Toggle({ type: ToggleType.Switch, isOn: this.model.isGrant })
.margin({ left: 10 })
.onTouchIntercept((event) => {
return HitTestMode.None
})
}
.onClick(() => {
this.onClickToggle?.()
})
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.SpaceBetween)
}
}
