DevecoStudio学习之权限申请

一、鸿蒙 Next 权限申请概览

鸿蒙 Next 系统为了保护用户数据和系统资源,对应用的权限进行了细致的分类管理。其中,系统授权和用户授权是两种主要的授权方式,它们各自适用于不同类型的权限,开发者需要根据应用的具体功能需求来选择合适的授权方式。这就好比在建造一座大厦时,需要根据不同区域的功能需求选择合适的建筑材料和施工工艺,以确保大厦的稳固与安全。

1. 系统授权:直接配置文件配置申请 (不需要询问用户)

系统授权,正如其名,是由系统在应用安装过程中自动完成的权限授予操作。这种授权方式适用于那些对系统或其他应用影响较小、不涉及用户敏感信息的权限。

例如,应用获取网络信息、查询自身基本信息等操作所需的权限,通常都采用系统授权方式。

2. 用户授权:需要用户手动确认的权限(必须提示用户主动授权)

与系统授权不同,用户授权则更加注重用户的知情权和选择权。当应用需要访问用户的敏感信息或执行可能影响用户隐私的操作时,

如使用摄像头、麦克风、读取通讯录等,就必须通过用户授权方式获得用户的明确许可。这确保了用户始终对自己的数据拥有控制权,就像在自己的私人领地设置了一道道关卡,只有经过用户亲自授权的应用才能进入并使用相关资源。

二、鸿蒙 Next 授权流程解析

1. 系统授权流程解析

🐡 权限判断与申请准备

在进行系统授权之前,开发者首先要明确应用所需的权限,并判断这些权限是否属于系统授权类型。这需要开发者对鸿蒙 Next 的权限体系有深入的了解,熟悉各种权限的分类和适用场景。可以将其类比为航海前的航线规划,只有明确目的地和路线,才能确保航行的顺利进行。

🐡 系统自动授予权限的过程

一旦确定应用所需的权限为系统授权类型,开发者只需在应用的配置文件中正确声明这些权限。当用户安装应用时,系统会自动识别并授予相应的权限,整个过程无需用户进行额外的操作。这就像在一家自动化餐厅,顾客只需选择自己想要的菜品(声明权限),餐厅的自动化系统(鸿蒙 Next 系统)就会自动将菜品送到顾客面前(授予权限)。

🐡 系统授权权限列表展示

以下是一些常见的系统授权权限及其说明:

🐡 在配置文件中声明权限
  • 配置文件的选择与定位

    用户授权的第一步是在应用的配置文件中声明所需的权限。在鸿蒙 Next 项目中,通常使用“module.json5”配置文件来进行权限声明。这个配置文件就像是应用的“说明书”,告诉系统应用需要哪些权限才能正常运行。
  • 权限声明的格式与规范
    在“module.json5”文件中,通过“requestPermissions”字段来声明权限。每个权限声明都包含“name”(权限名称)、“reason”(申请权限的原因)和“usedScene”(权限使用的场景)等属性。

    🔊说明: 以下"ohos.permission.PERMISSION1"、"ohos.permission.PERMISSION2"仅为样例示意,不存在该权限。请开发者根据实际需要,参照上表要求填写对应属性。
    {
      "module" : {
        // ...
        "requestPermissions":[
          {
            "name" : "ohos.permission.PERMISSION1",
            "reason": "$string:reason",
            "usedScene": {
              "abilities": [
                "FormAbility"
              ],
              "when":"inuse"
            }
          },
          {
            "name" : "ohos.permission.PERMISSION2",
            "reason": "$string:reason",
            "usedScene": {
              "abilities": [
                "FormAbility"
              ],
              "when":"always"
            }
          }
        ]
      }
    }

    其中,“name”必须是系统定义的有效权限名称,“reason”需要用简洁明了的语言向用户说明申请该权限的原因,并且要遵循一定的文案规范,如使用直白、具体、易理解的完整短句,避免使用被动语态,以句号结尾,同时要确保字符串长度适中,以适应多语言适配的需求。“usedScene”则用于指定权限使用的场景,包括使用权限的 UIAbility 或 ExtensionAbility 组件名称以及调用时机(“inuse”表示使用时,“always”表示始终)。

2. 用户授权流程详解

🐡 用户授权权限列表展示

以下是一些常见的用户授权权限及其说明:

🐡 在配置文件中声明权限

与“系统授权”一样,“用户授权”,在动态请求用户授权之前,应用应该先检查当前是否已经获得了所需的权限。在“module.json5”文件中,通过“requestPermissions”字段来声明权限。每个权限声明都包含“name”(权限名称)、“reason”(申请权限的原因)和“usedScene”(权限使用的场景)等属性。例如:

🔊说明: 以下"ohos.permission.PERMISSION1"、"ohos.permission.PERMISSION2"仅为样例示意,不存在该权限。请开发者根据实际需要,参照上表要求填写对应属性。
{
  "module" : {
    // ...
    "requestPermissions":[
      {
        "name" : "ohos.permission.PERMISSION1",
        "reason": "$string:reason",
        "usedScene": {
          "abilities": [
            "FormAbility"
          ],
          "when":"inuse"
        }
      },
      {
        "name" : "ohos.permission.PERMISSION2",
        "reason": "$string:reason",
        "usedScene": {
          "abilities": [
            "FormAbility"
          ],
          "when":"always"
        }
      }
    ]
  }
}

🐡 使用 API 动态请求用户授权
  • 权限检查与准备

    在动态请求用户授权之前,应用应该先检查当前是否已经获得了所需的权限。这可以通过调用“checkAccessToken()”函数来实现,该函数会返回“PERMISSION_GRANTED”或“PERMISSION_DENIED”,以指示当前权限的授予状态。这就好比在进入一个需要门票的场所之前,先检查自己是否已经购买了门票。
    import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    
    const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];
    
    async function checkPermissionGrant(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
      let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
      let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
    
      // 获取应用程序的accessTokenID
      let tokenId: number = 0;
      try {
        let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
        let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
        tokenId = 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}`);
      }
    
      // 校验应用是否被授予权限
      try {
        grantStatus = await atManager.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;
    }
    
    async function checkPermissions(): Promise<void> {
      let grantStatus: abilityAccessCtrl.GrantStatus = await checkPermissionGrant(permissions[0]);
    
      if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
        // 已经授权,可以继续访问目标操作
      } else {
        // 申请麦克风权限
      }
    }
  • 动态请求授权的 API 调用

    动态向用户申请权限是指在应用程序运行时向用户请求授权的过程。可以通过调用requestPermissionsFromUser()方法来实现。该方法接收一个权限列表参数,例如位置、日历、相机、麦克风等。用户可以选择授予权限或者拒绝授权。

    可以在UIAbility的onWindowStageCreate()回调中调用requestPermissionsFromUser()方法来动态申请权限,也可以根据业务需要在UI中向用户申请授权。

    应用在onWindowStageCreate()回调中申请授权时,需要等待异步接口loadContent()/setUIContent()执行结束后或在loadContent()/setUIContent()回调中调用requestPermissionsFromUser(),否则在Content加载完成前,requestPermissionsFromUser会调用失败。

    应用在UIExtensionAbility申请授权时,需要在onWindowStageCreate函数执行结束后或在onWindowStageCreate函数回调中调用requestPermissionsFromUser(),否则在ability加载完成前,requestPermissionsFromUser会调用失败。
    🐹:在UIAbility中向用户申请授权
    // 使用UIExtensionAbility:将import { UIAbility } from '@kit.AbilityKit' 替换为import { UIExtensionAbility } from '@kit.AbilityKit';
    import { abilityAccessCtrl, common, Permissions, UIAbility } from '@kit.AbilityKit';
    import { window } from '@kit.ArkUI';
    import { BusinessError } from '@kit.BasicServicesKit';
    
    const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];
    // 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
    function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
      let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
      // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
      atManager.requestPermissionsFromUser(context, permissions).then((data) => {
        let grantStatus: Array<number> = data.authResults;
        let length: number = grantStatus.length;
        for (let i = 0; i < length; i++) {
          if (grantStatus[i] === 0) {
            // 用户授权,可以继续访问目标操作
          } else {
            // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
            return;
          }
        }
        // 授权成功
      }).catch((err: BusinessError) => {
        console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
      })
    }
    // 使用UIExtensionAbility:将 UIAbility 替换为UIExtensionAbility
    export default class EntryAbility extends UIAbility {
      onWindowStageCreate(windowStage: window.WindowStage): void {
        // ...
        windowStage.loadContent('pages/Index', (err, data) => {
          reqPermissionsFromUser(permissions, this.context);
        // ...
        });
      }
    
      // ...
    }
    🐹:在UI中向用户申请授权
    import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    
    const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];
    // 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
    function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
      let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
      // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
      atManager.requestPermissionsFromUser(context, permissions).then((data) => {
        let grantStatus: Array<number> = data.authResults;
        let length: number = grantStatus.length;
        for (let i = 0; i < length; i++) {
          if (grantStatus[i] === 0) {
            // 用户授权,可以继续访问目标操作
          } else {
            // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
            return;
          }
        }
        // 授权成功
      }).catch((err: BusinessError) => {
        console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
      })
    }
    
    @Entry
    @Component
    struct Index {
      aboutToAppear() {
        // 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
        const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
        reqPermissionsFromUser(permissions, context);
      }
    
      build() {
        // ...
      }
    }
    !!!注意,如果用户拒绝了,再此调用requestPermissionsFromUser是不会再此弹出来的。如果还需要选项,就需要引导用户去设置里面授权。
  • 二次向用户申请授权

    requestPermissionOnSetting打开应用设置权限界面,可以参考打开应用设置权限界面。此节不详细说明。
    第一次被拒绝后,那么需要引导用户去设置中,授权。
    const secondAuthResults = await atManager.requestPermissionOnSetting(context, permissions)
    const isSecondAuth = secondAuthResults.every(s => s == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
    !!!注意,在这里设置会有三种情况。

    设置中授权界面一般会有三个选项:1、仅使用期间允许;2、每次使用询问;3、禁止;

    只有第一个选项,结果会返回授权了。第三个选项,好理解,就是不授权。但是要注意了,第二个选项也是返回不授权。但是我们明显是知道用户是授权了的。所以上面的代码返回false之后,还需要再次调用第一次授权的代码,再次询问用户是否授权了。

    下面是完整二次授权代码

    const secondAuthResults = await atManager.requestPermissionOnSetting(context, permissions)
    const isSecondAuth = secondAuthResults.every(s => s == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
    if (!isSecondAuth) {
        const isAuth = await firstReqPermissionFormUser(permissions, context)
        resolve(isAuth)
    } else {
        resolve(true)
    }
  • 完整请求权限工具类代

    import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';
    import { UIContext } from '@kit.ArkUI';
     
    import { BusinessError } from '@kit.BasicServicesKit';
     
    async function checkPermissionGrant(permission: Permissions): Promise<boolean> {
        let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
     
        // 获取应用程序的accessTokenID
        let tokenId: number = 0;
        try {
            let bundleInfo: bundleManager.BundleInfo =
                await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
            let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
            tokenId = 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}`);
        }
     
        // 校验应用是否被授予权限
        try {
            grantStatus = await atManager.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;
    }
     
     
    async function firstReqPermissionFormUser(permissions: Array<Permissions>, context: common.UIAbilityContext) {
        let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const data = await atManager.requestPermissionsFromUser(context, permissions)
        let grantStatus: Array<number> = data.authResults;
    return grantStatus.every(s => s == 0)
    }
     
    async function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext,
        uiContext: UIContext) {
        let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        try {
            // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
            const isAuth = await firstReqPermissionFormUser(permissions, context)
            if (isAuth) {
                return true
            }
     
            return await new Promise((resolve, reject) => {
                uiContext.showAlertDialog({
                    title: "系统提示",
                    message: "必须要授权才能使用,是否前往应用进行授权",
                    autoCancel: false,
                    primaryButton: {
                        value: "取消",
                        action: () => {
                            resolve(false)
                        }
                    },
                    secondaryButton: {
                        value: "前往授权",
                        action: async () => {
                            const secondAuthResults = await atManager.requestPermissionOnSetting(context, permissions)
                            const isSecondAuth =
                                secondAuthResults.every(s => s == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
                            if (!isSecondAuth) {
                                const isAuth = await firstReqPermissionFormUser(permissions, context)
                                resolve(isAuth)
                            } else {
                                resolve(true)
                            }
                        }
                    }
                })
            })
        } catch (err) {
            const error = err as BusinessError
            console.error(`Failed to request permissions from user. Code is ${error.code}, message is ${error.message}`);
        }
        return true
    }
     
    export async function requestPermissions(uiAbilityContext: common.UIAbilityContext, uiContext: UIContext,
        permissions: Permissions[]) {
        const ps = permissions.map((s) => {
            return checkPermissionGrant(s).then(isSuccess => {
                if (isSuccess) {
                    return true
                }
                return Promise.reject(s)
            })
        })
        const s = await Promise.allSettled(ps)
        const rejectedPs =
            s.filter(item => item.status == "rejected").map((item: PromiseRejectedResult) => item.reason as Permissions)
     
        if (rejectedPs.length == 0) {
            return true
        }
     
        return await reqPermissionsFromUser(rejectedPs, uiAbilityContext, uiContext)
    }
🐡 处理用户授权结果
  • 授权成功后的操作

    当用户授权成功后,应用可以继续执行需要该权限的操作。例如,如果应用申请了相机权限并获得授权,就可以打开相机进行拍照或录像操作。这就像获得了进入宝库的钥匙,可以顺利取出宝藏(执行相应功能)。
  • 授权失败后的应对策略

    如果用户拒绝授权,应用需要友好地提示用户授权的必要性,并引导用户前往系统设置中手动授予权限。同时,应用应该确保在用户未授权的情况下,不会影响其他无关功能的正常使用。这就好比在一扇紧闭的门前,向用户解释门后的精彩内容,并引导用户找到打开门的正确方法,而不是强行推门或影响周围环境的正常秩序。

3. 不同权限类型申请方式总结

为了更清晰地展示不同权限类型的申请方式,我们通过以下表格进行总结:

三、示例代码:请求麦克风权限

以下是一个完整的示例代码,演示了如何在鸿蒙 Next 应用中请求麦克风权限:

import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 定义需要申请的权限列表,这里仅包含麦克风权限
const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];

// 检查当前应用是否已被授予指定权限
async function checkPermissionGrant(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
  // 获取应用程序的 accessTokenID
  let tokenId: number = 0;
  try {
    let bundleInfo: bundleManager.BundleInfo =
      await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
    let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
    tokenId = 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}`);
  }
  // 校验应用是否被授予权限
  try {
    grantStatus = await atManager.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;
}

// 检查权限并根据结果进行相应操作
async function checkPermissions(): Promise<void> {
  let grantStatus: abilityAccessCtrl.GrantStatus = await checkPermissionGrant(permissions[0]);
  if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
    // 已经授权,可以继续访问目标操作,这里可以添加使用麦克风的相关代码
    console.log('已获得麦克风权限,可以进行录音等操作。');
  } else {
    // 申请麦克风权限
    reqPermissionsFromUser(permissions);
  }
}

// 使用 API 动态请求用户授权
function reqPermissionsFromUser(permissions: Array<Permissions>): void {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  atManager.requestPermissionsFromUser(globalThis.context as common.UIAbilityContext, permissions).then((data) => {
    let grantStatus: Array<number> = data.authResults;
    let length: number = grantStatus.length;
    for (let i = 0; i < length; i++) {
      if (grantStatus[i] === 0) {
        // 用户授权,可以继续访问目标操作,这里可以添加使用麦克风的相关代码
        console.log('用户已授权麦克风权限,可以进行录音等操作。');
      } else {
        // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
        console.log('用户拒绝授权麦克风权限,请前往系统设置中手动授予权限。');
        return;
      }
    }
    // 授权成功
  }).catch((err: BusinessError) => {
    console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
  });
}

// 在应用启动或需要使用麦克风的地方调用 checkPermissions() 函数来检查和申请权限
checkPermissions();

在上述代码中,首先定义了需要申请的麦克风权限。然后,通过“checkPermissionGrant()”函数检查应用当前是否已获得该权限。如果未获得权限,则调用“reqPermissionsFromUser()”函数向用户发起授权请求。根据用户的授权结果,应用会在控制台输出相应的提示信息,并在授权成功后可以继续执行使用麦克风的相关操作。

总之,在鸿蒙 Next 应用开发中,正确处理权限申请是至关重要的。开发者需要深入理解系统授权和用户授权的机制与流程,根据应用的实际需求合理选择授权方式,并严格按照规范进行权限声明和请求操作。只有这样,才能确保应用在保障用户数据安全的前提下,提供稳定、优质的服务。

posted on 2024-11-06 19:43  梁飞宇  阅读(445)  评论(0)    收藏  举报