前言
系統(tǒng)提供了一種允許應用訪問系統(tǒng)資源(如:通訊錄等)和系統(tǒng)能力(如:訪問攝像頭、麥克風等)的通用權(quán)限訪問方式,來保護系統(tǒng)數(shù)據(jù)(包括用戶個人數(shù)據(jù))或功能,避免它們被不當或惡意使用。
權(quán)限組和子權(quán)限
為了盡可能減少系統(tǒng)彈出的權(quán)限彈窗數(shù)量,優(yōu)化交互體驗,系統(tǒng)將邏輯緊密相關(guān)的user_grant權(quán)限組合在一起,形成多個權(quán)限組。
當應用請求權(quán)限時,同一個權(quán)限組的權(quán)限將會在一個彈窗內(nèi)一起請求用戶授權(quán)。權(quán)限組中的某個權(quán)限,稱之為該權(quán)限組的子權(quán)限。
例如圖片和視頻讀取權(quán)限和寫入權(quán)限,可以放到一起合并申請,我們可以看到申請權(quán)限的 api 中權(quán)限的參數(shù)接收的是一個數(shù)組類型: requestPermissionsFromUser(context: Context, permissionList: Array<Permissions>, requestCallback: AsyncCallback<PermissionRequestResult>): void
具體哪些權(quán)限屬于同個權(quán)限組,可以參考應用權(quán)限組列表
在配置文件中聲明權(quán)限
在Entry 模塊的module.json5 中配置"requestPermissions",配置應用中需要的權(quán)限類型,示例中配置了相機權(quán)限、圖片和視頻的讀寫權(quán)限,其中圖片和視頻的讀和寫可以合并為一個權(quán)限組,詳見后面示例。
"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"
}
}
]
檢查權(quán)限是否已授權(quán)
在申請權(quán)限之前,先通過checkAccessToken()校驗權(quán)限是否已授權(quán)。
/**
* 檢查權(quán)限狀態(tài)
* @param permission 權(quán)限
*/
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}`);
}
// 權(quán)限狀態(tài)
let grantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
// 校驗應用是否被授予權(quán)限。
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;
}
向用戶申請權(quán)限
向用戶申請權(quán)限可以通過requestPermissionsFromUser()實現(xiàn),系統(tǒng)不鼓勵頻繁彈窗打擾用戶。如果用戶拒絕授權(quán),將無法再次彈窗。但我們可能有這么一個場景,需要在非首次請求用戶權(quán)限獲取到未授權(quán)狀態(tài)時,引導用戶到系統(tǒng)應用設(shè)置去進行開啟權(quán)限,如何判斷呢?requestPermissionsFromUser的回調(diào)中在 api12+增加了dialogShownResults屬性,用于判斷是否進行了彈窗,因此我們請求權(quán)限的方法除了回調(diào)授權(quán)狀態(tài),還回調(diào)了是否有彈窗。
/**
* 請求權(quán)限
* @param permissions 權(quán)限列表
*/
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()
})
})
}
引導用戶跳轉(zhuǎn)系統(tǒng)應用設(shè)置
當請求用戶授權(quán)首次彈窗后,無法再次彈窗,但用戶未來可能希望更改授權(quán)狀態(tài),應用需引導用戶在系統(tǒng)設(shè)置中手動授予權(quán)限,可以從應用中直接跳轉(zhuǎn)到系統(tǒng)應用設(shè)置中。
/**
* 跳轉(zhuǎn)系統(tǒng)應用設(shè)置
* @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
/**
* 觀察數(shù)組
*/
export class ObservedArray<T> extends Array<T> {
constructor(...args: T[]) {
super(...args)
}
}
UserPermissionModel.ets
import { Permissions } from '@kit.AbilityKit'
import { UserPermissionHandle } from './UserPermissionHandle'
/**
* 用戶權(quán)限模型
*/
@Observed
export class UserPermissionModel {
name: string
permissions: Permissions[]
isGrant: boolean = false
constructor(name: string, permission: Permissions[]) {
this.name = name
this.permissions = permission
}
// 檢查權(quán)限
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 // 其中一個未授權(quán),權(quán)限組判定為未授權(quán)
}
}
this.isGrant = isGrant
} catch (err) {
this.isGrant = false
}
}
// 請求權(quán)限
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";
/**
* 用戶權(quán)限處理類
*/
export class UserPermissionHandle {
/**
* 檢查權(quán)限狀態(tài)
* @param permission 權(quán)限
*/
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}`);
}
// 權(quán)限狀態(tài)
let grantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
// 校驗應用是否被授予權(quán)限。
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;
}
/**
* 請求權(quán)限
* @param permissions 權(quán)限列表
*/
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()
})
})
}
/**
* 跳轉(zhuǎn)系統(tǒng)應用設(shè)置
* @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('相機權(quán)限', ['ohos.permission.CAMERA']),
new UserPermissionModel('相冊權(quán)限', ['ohos.permission.READ_IMAGEVIDEO', 'ohos.permission.WRITE_IMAGEVIDEO']),
new UserPermissionModel('麥克風權(quán)限', ['ohos.permission.MICROPHONE']),
new UserPermissionModel('位置信息權(quán)限', ['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) {
// 未授權(quán),且無彈窗,則跳轉(zhuǎn)系統(tǒng)應用設(shè)置
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('權(quán)限管理')
}
}
@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)
}
}
