鸿蒙APP权限管理

前言

系统提供了一种允许应用访问系统资源(如:通讯录等)和系统能力(如:访问摄像头、麦克风等)的通用权限访问方式,来保护系统数据(包括用户个人数据)或功能,避免它们被不当或恶意使用。

权限组和子权限

为了尽可能减少系统弹出的权限弹窗数量,优化交互体验,系统将逻辑紧密相关的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)
  }
}
图片和视频读取&图片和视频写入两者合并为一个权限组一起申请

参考资料

声明权限
向用户申请授权
应用权限组列表

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容