# 桃夭 **Repository Path**: zhongte/TaoYao ## Basic Information - **Project Name**: 桃夭 - **Description**: 桃夭是鸿蒙系统上的一个权限请求框架,封装了权限请求逻辑,采用链式调用的方式请求权限,极大的简化了权限请求的代码。 - **Primary Language**: TypeScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 85 - **Forks**: 16 - **Created**: 2024-05-05 - **Last Updated**: 2025-09-04 ## Categories & Tags **Categories**: harmonyos-permission **Tags**: 权限, 访问控制, 鸿蒙 ## README # 桃夭 ### 申请权限的一般步骤 * 判断是否有权限,如果有权限,直接进行下一步。 * 如果没有权限,则开始申请权限。 * 如果用户授权,进行下一步。 * 如果用户拒绝授权,后面再次申请权限,系统为了不打扰用户,将不会出现系统的权限弹窗。在用户拒绝授权后,需要弹窗提示用户必须授权才能访问当前功能,并引导用户到系统设置中打开相应的权限。

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

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

本项目基于开源鸿蒙5.0开发,最低兼容到API 12,请将DevEco Studio升级到最新版。

### 桃夭名称来源 桃夭一词出自古代第一部诗歌总集《诗经》中《诗经·桃夭》,“桃之夭夭,灼灼其华。”桃花怒放千万朵,色彩鲜艳红似火。 ### 下载安装 ``` ohpm install @shijing/taoyao ``` ### 申请运行时权限 ``` TaoYao.with(this) .runtime() // 要申请的权限 .permission(permissions) .onGranted(() => { // 权限申请成功 }) .onDenied(() => { // 权限申请失败 }) .request() ``` **申请权限变得如此之简单。** ### 已废弃的两个权限 允许应用读取用户外部存储中的媒体文件信息 ``` ohos.permission.READ_MEDIA ``` 允许应用读写用户外部存储中的媒体文件信息 ``` ohos.permission.WRITE_MEDIA ``` 从5.0,API 12开始,上述两个权限已经废弃。推荐方案(无需申请权限):使用图片/视频选择器读取媒体库的图片与视频;使用保存控件保存媒体库的图片与视频。 ### 跳转到系统设置 下面这张图是系统权限设置页面。 ![系统权限设置页面](./taoyao/screenshots/system_setting.png)
下面这张图是系统权限设置弹窗。 ![系统权限设置页面](./taoyao/screenshots/system_dialog.png)
在api 12之前,只能跳转到系统权限设置页面。从API 12开始,可以直接拉起权限设置弹框,引导用户授予权限。这样的好处是,当系统设置权限弹窗关闭后,可以直接知道是否有权限。 默认情况下,桃夭会拉起系统权限设置弹窗,当系统权限设置弹窗关闭后,会通过回调告知是否有权限。
但是,有可能拉起系统权限设置弹窗会失败,目前发现ohos.permission.READ_HEALTH_DATA读取健康数据权限无法直接拉起系统设置弹窗,只能跳转到系统设置页面。 当拉起系统权限设置弹窗失败后,会回调onFailed。当回调回调onFailed,如果还是想从系统设置页面回来后是否有权限,可以在onPageShow方法里面申请权限。 ``` TaoYao .showSystemPermissionDialog(this.context, this.permissions) .onGranted(() => { // 直接拉起系统权限设置弹窗后,用户授权 this.toast('直接拉起系统权限设置弹窗后,有权限了') }) .onDenied(() => { // 直接拉起系统权限设置弹窗后,用户未授权 this.toast('直接拉起系统权限设置弹窗后,没权限') }) .onFailed(() => { /* * 拉起系统设置弹窗失败,无法直接判断用户是否在系统权限设置弹窗授权, * 只能跳转到系统权限设置页面,可以在onPageShow方法里面申请权限。 * 目前发现ohos.permission.READ_HEALTH_DATA健康数据权限无法直接拉起系统设置弹窗,只能跳转到系统设置页面。 */ console.log("拉起系统设置弹窗失败,无法直接判断用户是否在系统权限设置弹窗授权,只能跳转到系统设置页面") }) ``` ### 功能被禁用的处理方式 系统提供了超级隐私模式,在系统设置打开超级隐私模式后,相机、麦克风、位置将不可用。在获取相机权限、麦克风权限和位置权限后,如果开启了超级隐私模式,需要引导用户关闭超级隐私模式。 ``` if (!TaoYao.isLocationEnabled()) { // 开启了超级隐私模式或者未打开定位开关,拉起全局开关弹窗引导用户关闭超级隐私模式或者打开定位开关 TaoYao.requestLocationGlobalSwitch(context).then((isOpen => { if (isOpen) { // 定位开关已打开 } else { // 定位开关已关闭 } })) } else { // 定位开关已打开 } TaoYao.isMicrophoneMute().then(isMute => { if (isMute) { // 开启了超级隐私模式,麦克风被静音,拉起全局开关弹窗引导用户关闭超级隐私模式 TaoYao.requestMicrophoneGlobalSwitch(context).then((isOpen => { if (isOpen) { // 麦克风已开启 } else { // 麦克风已静音 } })) } else { // 麦克风开启 } }) if (TaoYao.isCameraMuted(context)) { // 开启了超级隐私模式,相机不可用,拉起全局开关弹窗引导用户关闭超级隐私模式 TaoYao.requestCameraGlobalSwitch(context).then(isOpen => { if (isOpen) { // 相机已开启 } else { // 相机已关闭 } }) } else { // 相机已开启 } ``` ### 申请通知权限 如果用户拒绝通知权限,只能跳转到系统设置页面,系统权限设置弹窗不支持通知权限 ``` TaoYao.with(this.context) .notification() .permission() .onGranted(() => { // 通知权限申请成功 this.toast() }) .onDenied(() => { // 通知权限只能跳转到系统设置页面,系统权限设置弹窗不支持通知权限 TaoYao.goToSettingPage(this.context) }) .request() ``` ### 判断是否有权限 有的时候仅仅只需判断是否有运行时权限,并不申请权限。 ``` /** * 仅仅检测是否有权限,如果没有权限,不会申请权限 * * @param permissions * @returns true 有权限 */ static hasPermission(permissions: Array): boolean { const generalChecker = new GeneralChecker() return generalChecker.hasPermission(permissions) } ``` ### 判断是否有通知权限 判断是否有通知权限,如果没有通知权限,不会申请通知权限 ``` /** * 判断是否有通知权限,如果没有通知权限,不会申请通知权限 * * @returns true支持,false不支持 */ static isNotificationEnabled(): Promise { return NotificationPermissionChecker.isNotificationEnabled() } ``` ### 判断是否支持分布式通知 ``` /** * 判断是否支持分布式通知 * * @returns true支持,false不支持 */ static isDistributedEnabled(): Promise { return NotificationPermissionChecker.isDistributedEnabled() } ``` ### 系统选择器 系统选择器已经获取了对应权限的预授权,开发者使用系统选择器时,无需再次申请权限也可临时受限访问对应的资源。例如,当应用需要读取用户图片时,可通过使用照片选择器,在用户选择所需要的图片资源后,直接返回该图片资源,而不需要授予应用读取图片文件的权限。
#### 联系人选择器 通过联系人选择器获取联系人,不需要申请通讯录权限。 ``` TaoYao.with(this.context) .contact() .onSuccess((data) => { // 联系人列表 if (data.length > 0) { // 联系人名称 console.log("yunfei", data[0].name?.fullName) // 联系人号码 console.log("yunfei", data[0]?.phoneNumbers?.[0]?.phoneNumber) } }) .onError((err) => { console.log(err.message) }) .selectContacts(new ContactBuilder() // 可以选择多个联系人 .setMultiSelect(true)) ``` #### 相机选择器 拉起系统相机不需要申请相机权限。 ``` TaoYao.with(this.context) .camera() .onSuccess((uri) => { // 拍照或者录像的文件沙箱路径 console.log(uri) }) .onError((err) => { console.log(err.stack) }) .openSystemCamera(new CameraBuilder() // 后置相机,默认使用后摄 .setCameraSelector(CameraSelector.CAMERA_POSITION_BACK) // 可以只要拍照,只要录像,默认拍照和录像都有 .setUseCase([UseCase.PHOTO, UseCase.VIDEO]) // 文件保存路径,可以不设置 //.setSaveUri("") // 录制视频最大时长,可以不设置 //.setVideoMaxDuration() ) ``` #### 图片、视频选择器 拉起系统图库不需要申请存储权限,只能获取选中的图片、视频 ``` TaoYao.with(this.context) .media() .onSuccess((uris) => { uris.forEach((uri) => { console.log("yunfei", uri) }) }) .onError((err) => { console.log(err.stack) }) .select(new MediaBuilder() // 选择媒体文件的最大数目 .setMaxSelectNumber(10) // 可选择的媒体文件类型,图片类型、视频类型、图片和视频类型、动态照片类型 .setMediaMineType(MediaMimeType.IMAGE_VIDEO_TYPE) ) ``` #### 文档选择器 拉起文档选择器不需要申请权限,只能获取选中的文档 ``` TaoYao.with(this.context) .document() .onSuccess((uris) => { uris.forEach((uri) => { console.log("yunfei", uri) }) }) .onError((err) => { console.log(err.stack) }) .select(new DocumentBuilder() // 选择媒体文件的最大数目 .setMaxSelectNumber(10) // 指定选择的文件或者目录路径(可选) //.setDefaultFilePathUri("") // 选择是否对指定文件或目录授权,true为授权,当为true时,defaultFilePathUri为必选参数,拉起文管授权界面;false为非授权,拉起常规文管界面(可选) //.setAuthMode(false) // 选择文件的后缀类型['后缀类型描述|后缀类型'](可选) 若选择项存在多个后缀名,则每一个后缀名之间用英文逗号进行分隔(可选),后缀类型名不能超过100,选择所有文件:'所有文件(*.*)|.*'; // 例如:['图片(.png, .jpg)|.png,.jpg', '文档|.txt', '视频|.mp4', '.pdf'] .setFileSuffixFilters(['文档|.docx']) ) ``` #### 音频选择器 拉起音频选择器不需要申请权限,只能获取选中的音频 ``` TaoYao.with(this.context) .audio() .onSuccess((uris) => { uris.forEach((uri) => { console.log("yunfei", uri) }) }) .onError((err) => { console.log(err.stack) }) // 目前音频选择器不支持参数配置,默认可以选择所有类型的用户文件。 .select(new AudioBuilder()) ``` ### 实现原理 实现原理请查看[实战鸿蒙,实现一款权限请求框架](https://juejin.cn/post/7394786709647081472)。
本项目在开发过程中,借鉴了开源项目[AndPermission](https://github.com/yanzhenjie/AndPermission),感谢该作者。