1. 首页 > 科技 > 数码资讯

HarmonyOS 实现一款权限请求框架 NEXT体验官#实战鸿蒙

想了解更多关于开源的内容,请访问:

鸿蒙开发者社区

一、申请权限的一般步骤

每次申请权限的时候,都需要经过以上几个步骤,当申请的权限越来越多,大量的重复代码就出现了。为了减少重复代码,我封装了一个权限请求框架。

二、权限请求框架

桃夭是鸿蒙系统上的一款权限请求框架,封装了权限请求逻辑,采用链式调用的方式请求权限,极大的简化了权限请求的代码,同时支持在UI、UIAbility、UIExtensionAbility里面申请权限。需要注意的是,应用在UIExtensionAbility申请授权时,需要在onWindowStageCreate函数执行结束后或在onWindowStageCreate函数回调中申请权限。

本项目基于开源鸿蒙4.1开发,最低兼容到API 11,请将DevEco Studio升级到最新版,DevEco Studio版本低于5.0.3.403可能无法编译。

桃夭一词出自古代第一部诗歌总集《诗经》中《诗经·桃夭》,“桃之夭夭,灼灼其华。”桃花怒放千万朵,色彩鲜艳红似火。

四、桃夭的使用方式

下载

ohpm install @shijing/taoyao

申请权限

TaoYao.with(this).runtime()// 要申请的权限.permission(permissions).onGranted(() => {// 权限申请成功}).onDenied(() => {// 权限申请失败}).request()

申请权限变得如此之简单。

五、实现原理

1.如何支持在UI、UIAbility、UIExtensionAbility里面申请权限。

可以使用联合类型,也可以使用重载。这里通过重载的方式来实现在UI、UIAbility、UIExtensionAbility里面申请权限。

/*** 直接在UIExtensionAbility中申请权限** @param uiAbility* @returns*/static with(extensionAbility: UIExtensionAbility): IAccessControl;/*** 在UI中向用户申请授权** @param context* @returns*/static with(context: common.UIAbilityContext): IAccessControl;/*** 直接在UIAbility中申请权限** @param uiAbility* @returns*/static with(uiAbility: UIAbility): IAccessControl;static with(context: common.UIAbilityContext | UIAbility | UIExtensionAbility): IAccessControl {if (context instanceof UIAbility) {return new AccessControl(new UIAbilityOrigin(context))} else if (context instanceof UIExtensionAbility) {return new AccessControl(new UIExtensionAbilityOrigin(context))} else {return new AccessControl(new ContextOrigin(context))}}

UI、UIAbility、UIExtensionAbility里面最重要就是Context对象,申请权限的时候需要传入Context对象,我们需要从UI、UIAbility、UIExtensionAbility里面获取Context对象。这里采用策略模式。创建接口Origin,Origin代表从哪申请权限,定义getContext方法,由子类实现该方法。

/** * 需要UI、UIAbility、UIExtensionAbility申请权限,同时获取Context对象。 */export interface Origin {/*** 获取context对象** @returns*/getContext(): Context}

ContextOrigin代表在在UI中申请权限,实现Origin接口,重写getContext方法。

/** * 在UI中申请权限 */export class ContextOrigin implements Origin {private context: common.UIAbilityContextconstructor(context: common.UIAbilityContext) {this.context = context}getContext(): Context {return this.context}}

UIAbilityOrigin代表在在UIAbility中申请权限,同样实现Origin接口,重写getContext方法。

/** * 在UIAbility中申请权限 */export class UIAbilityOrigin implements Origin {private uiAbility: UIAbilityconstructor(uiAbility: UIAbility) {this.uiAbility = uiAbility}getContext(): Context {return this.uiAbility.context}}

UIExtensionAbilityOrigin代表在在UIExtensionAbility中申请权限,同样实现Origin接口,重写getContext方法。

/** * 在UIExtensionAbility中申请权限 */export class UIExtensionAbilityOrigin implements Origin {private uiExtensionAbility: UIExtensionAbilityconstructor(uiExtensionAbility: UIExtensionAbility) {this.uiExtensionAbility = uiExtensionAbility}getContext(): Context {return this.uiExtensionAbility.context}}

2.检测申请的权限是否在module.json5文件中声明

申请的权限必须在module.json5文件中声明,否则桃夭会直接抛异常。如何检测申请的权限是否在配置文件中声明?如下代码,通过bundleManager对象获取应用信息,之后就可以获取应用在配置文件中声明的权限了。如果要申请的权限没有module.json5文件中声明,那就会抛异常。

/*** 检查要申请的权限是否在module.json5文件中声明** @param permissions 要申请的权限*/private checkCommonConfig(permissions: Array) {const bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;// 同步获取在module.json5文件中声明的所有权限const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags)const reqPermissionDetails = bundleInfo.reqPermissionDetailsif (ArrayUtils.isEmpty(reqPermissionDetails)) {throw new Error('请在module.json5文件中声明权限')}const reqPermissions = new ArrayList()reqPermissionDetails.forEach(reqPermissionDetail => {reqPermissions.add(reqPermissionDetail.name)})permissions.forEach((permission) => {if (!reqPermissions.has(permission)) {// 要申请的权限没有module.json5文件中声明throw new Error(`请在module.json5文件中声明${permission}权限`)}})}

3.检测其它配置

对于位置权限,有三种情况:

第一:申请模糊位置权限,大部分情况下,不会申请模糊位置权限,更多的是第二种情况。

第二:申请精确位置权限。

第三:申请后台位置权限。针对位置权限,我们需要额外的配置下。

如果用户申请精确位置权限,那就要先申请粗略位置权限。

如果用户申请后台位置权限,那就先申请模糊位置权限和精确位置权限。当同意这两个权限后,弹窗提示用户到系统设置中打开相应的权限,用户在设置界面中的选择“始终允许”应用访问位置信息权限,应用就获取了后台位置权限。

/*** 检查权限的其它配置** @param permissions*/checkOtherConfig(permissions: Array) {const locationPermissionIndex = permissions.indexOf(this.LOCATION_PERMISSION)const locationBackgroundIndex = permissions.indexOf(this.LOCATION_IN_BACKGROUND)if (locationPermissionIndex >= 0 && locationBackgroundIndex < 0) {/** 对于位置权限,有两种情况* 第一:申请模糊位置权限,大部分情况下,不会申请模糊位置权限,更多的是第二种情况。* 第二:申请精确位置权限,需要先申请模糊位置权限。*/permissions = []permissions.push(this.APPROXIMATELY_LOCATION)permissions.push(this.LOCATION_PERMISSION)}if (locationBackgroundIndex >= 0) {// 申请后台位置权限,需要先申请模糊位置权限和精确位置权限。当用户点击弹窗授予前台位置权限后,应用通过弹窗、提示窗等形式告知用户前往设置界面授予后台位置权限。permissions = []permissions.push(this.APPROXIMATELY_LOCATION)permissions.push(this.LOCATION_PERMISSION)permissions.push(this.LOCATION_IN_BACKGROUND)}this.setNewPermission(permissions)}

4.判断是否有权限

当所有的检测都通过后,就可以判断是否有权限了。调用checkAccessToken()方法来校验当前是否已经授权。如果已经授权,则回调告知调用者已经有权限,否则需要进行下一步操作,即向用户申请授权。

hasPermission(permissions: Array): boolean {for (let i = 0; i < permissions.length; i++) {const permission = permissions[i]let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;// 获取应用程序的accessTokenIDlet tokenId: number = 0;try {let bundleInfo = bundleManager.getBundleInfoForSelfSync(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}`);}// 校验应用是否被授予权限grantStatus = atManager.checkAccessTokenSync(tokenId, permission);if (grantStatus !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {return false}}return true}

5.申请权限

调用requestPermissionsFromUser(),如果用户授权,则调用mOnGranted。如果用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限。

/*** 申请权限** @param permissions*/requestPermission(permissions: Permissions[]) {let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗atManager.requestPermissionsFromUser(this.origin.getContext(), permissions).then((data) => {let grantStatus: Array =>

6.系统设置弹窗

用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限。但在跳转系统设置之前,需要弹窗提示用户,这里提供一个默认的弹窗。如果这个弹窗不满足你的要求,你可以改掉。当用户在弹窗里面点击取消,则隐藏弹窗。当用户在弹窗里面点击去设置,则跳转到系统设置页面。

import { TaoYao } from '@shijing/taoyao/Index'import { common, Permissions } from '@kit.AbilityKit'import { hilog } from '@kit.PerformanceAnalysisKit'/** * 跳转系统设置之前,需要先弹窗 */@CustomDialogexport struct PermissionDialog {private title: string = '权限设置'private subtitle?: Resourceprivate left: string = '取消'private right: string = '去设置'private permissions = new Array()private context = getContext(this) as common.UIAbilityContextcontroller: CustomDialogControlleraboutToAppear(): void {if (this.permissions.indexOf(('ohos.permission.ACCESS_BLUETOOTH' as Permissions)) >= 0) {this.subtitle = $r('app.string.access_bluetooth')} else if (this.permissions.indexOf(('ohos.permission.MEDIA_LOCATION' as Permissions)) >= 0) {this.subtitle = $r('app.string.media_location')} else if (this.permissions.indexOf(('ohos.permission.APP_TRACKING_CONSENT' as Permissions)) >= 0) {this.subtitle = $r('app.string.app_tracking_consent')} else if (this.permissions.indexOf(('ohos.permission.ACTIVITY_MOTION' as Permissions)) >= 0) {this.subtitle = $r('app.string.activity_motion')} else if (this.permissions.indexOf(('ohos.permission.CAMERA' as Permissions)) >= 0) {this.subtitle = $r('app.string.camera')} else if (this.permissions.indexOf(('ohos.permission.DISTRIBUTED_DATASYNC' as Permissions)) >= 0) {this.subtitle = $r('app.string.distributed_datasync')} else if (this.permissions.indexOf(('ohos.permission.LOCATION_IN_BACKGROUND' as Permissions)) >= 0) {this.subtitle = $r('app.string.location_in_background')} else if (this.permissions.indexOf(('ohos.permission.LOCATION' as Permissions)) >= 0) {this.subtitle = $r('app.string.location')} else if (this.permissions.indexOf(('ohos.permission.APPROXIMATELY_LOCATION' as Permissions)) >= 0) {this.subtitle = $r('app.string.approximately_location')} else if (this.permissions.indexOf(('ohos.permission.MICROPHONE' as Permissions)) >= 0) {this.subtitle = $r('app.string.microphone')} else if (this.permissions.indexOf(('ohos.permission.READ_CALENDAR' as Permissions)) >= 0) {this.subtitle = $r('app.string.read_calendar')} else if (this.permissions.indexOf(('ohos.permission.WRITE_CALENDAR' as Permissions)) >= 0) {this.subtitle = $r('app.string.write_calendar')} else if (this.permissions.indexOf(('ohos.permission.READ_HEALTH_DATA' as Permissions)) >= 0) {this.subtitle = $r('app.string.read_health_data')} else if (this.permissions.indexOf(('ohos.permission.READ_MEDIA' as Permissions)) >= 0) {this.subtitle = $r('app.string.read_media')} else if (this.permissions.indexOf(('ohos.permission.WRITE_MEDIA' as Permissions)) >= 0) {this.subtitle = $r('app.string.write_media')}}build() {Column() {Text(this.title).fontSize(20).fontColor('#151724')Text(this.subtitle).fontColor('#151724').fontSize(15).margin({top: 30})Row() {Button(this.left).fontColor('#585a5c').borderRadius(24).backgroundColor('#eeeeee').width('40%').height(48).margin({right: 20}).onClick(() => {this.controller.close()})Button(this.right).fontColor('#ffffff').borderRadius(24).backgroundColor('#4b54fa').width('40%').height(48).onClick(() => {this.controller.close()TaoYao.goToSettingPage(this.context)})}.margin({top: 30}).justifyContent(FlexAlign.SpaceBetween)}.width('100%').borderRadius(20).backgroundColor('#ffffff').padding({left: 24, right: 24, top: 30, bottom: 28})}}

7.跳转到设置页面

使用下面的代码即可跳转到系统设置页面。构建一个want对象,指定bundleName、abilityName、uri、parameters等参数,调用startAbility。

function openPermissionsInSystemSettings(context: common.UIAbilityContext): void {let wantInfo: Want = {bundleName: 'com.huawei.hmos.settings', // 系统设置的包名abilityName: 'com.huawei.hmos.settings.MainAbility', // 系统设置权限页面的类名uri: 'application_info_entry',parameters: {pushParams: 'com.example.myapplication' // 应用的包名,也就是打开指定应用的详情页面}}context.startAbility(wantInfo).then(() => {// ...}).catch((err: BusinessError) => {// ...})

目前只有华为手机使用了开源鸿蒙系统,不排除后续会有其它的厂商使用开源鸿蒙系统,到时want对象的bundleName、abilityName、uri可能会不一样。在这种情况下,上面的代码就会有兼容性问题。这就需要针对不同的品牌,创建不同的want对象。这里采用策略模式。如下代码,创建SettingWant接口,定义getWant方法,由子类实现该方法,也就是由子类来创建want对象。

export interface SettingWant {/*** 获取want对象** @param bundleName* @returns*/getWant(bundleName: string): Want}

新建DefaultSettingWant类,DefaultSettingWant是一个默认创建Want对象的子类。

/** * 默认获取的want参数 */export class DefaultSettingWant implements SettingWant {getWant(bundleName: string): Want {let wantInfo: Want = {bundleName: 'com.huawei.hmos.settings',abilityName: 'com.huawei.hmos.settings.MainAbility',uri: 'application_info_entry',parameters: {pushParams: bundleName // 打开指定应用的详情页面}}return wantInfo}}

对于华为手机,我们就继承DefaultSettingWant,直接使用默认创建的Want对象。

/** * 获取华为手机上的want参数 */export class HuaWeiSettingWant extends DefaultSettingWant {}

如下代码,先创建SettingWant对象,通过deviceInfo.brand判断品牌,如果是华为手机,则创建HuaWeiSettingWant。调用getWant获取到Want对象,调用startAbility跳转到系统设置。

gToSettingPage(): void {const bundleName = this.getContext().abilityInfo.bundleNamelet settingWant: SettingWantif (deviceInfo.brand === "HUAWEI") {settingWant = new HuaWeiSettingWant()} else {settingWant = new DefaultSettingWant()}const want = settingWant.getWant(bundleName)if (this.origin instanceof UIExtensionAbilityOrigin) {// 在UIExtensionAbility中跳转到系统设置页面this.startAbilityFromUIExtensionAbility(want)} else {// 在UI或者UIAbility中跳转到系统设置页面this.startAbilityFromUIAbility(want)}} /*** 在UIExtensionAbility中跳转到系统设置页面** @param want*/private startAbilityFromUIExtensionAbility(want: Want) {(this.origin.getContext() as common.UIExtensionContext).startAbility(want).then(() => {// 跳转成功}).catch((err: BusinessError) => {console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);})}/*** 在UI或者UIAbility中跳转到系统设置页面** @param want*/private startAbilityFromUIAbility(want: Want) {this.getContext().startAbility(want).then(() => {// 跳转成功}).catch((err: BusinessError) => {console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);})}getContext(): common.UIAbilityContext {return (this.origin.getContext()) as common.UIAbilityContext}

六、源码

更多具体的代码,请下载 源码 或者查看 OpenHarmony三方库中心仓 。

想了解更多关于开源的内容,请访问:

鸿蒙开发者社区

本网站的文章部分内容可能来源于网络和网友发布,仅供大家学习与参考,如有侵权,请联系站长进行删除处理,不代表本网站立场,转载者并注明出处:https://jmbhsh.com/shumazixun/36165.html

联系我们

QQ号:***

微信号:***

工作日:9:30-18:30,节假日休息