NEXT体验官#实战鸿蒙 HarmonyOS 成功一款权限恳求框架

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

鸿蒙开发者社区

一、放开权限的普通步骤

每次放开权限的时刻,都须要经过以上几个步骤,当放开的权限越来越多,少量的重复代码就产生了。为了缩小重复代码,我封装了一个权限恳求框架。

二、权限恳求框架

桃夭是鸿蒙系统上的一款权限恳求框架,封装了权限恳求逻辑,驳回链式调用的方式恳求权限,极大的简化了权限恳求的代码,同时支持在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三方库核心仓 。

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

鸿蒙开发者社区

您可能还会对下面的文章感兴趣: