☰
Flutter 3.47 鸿蒙三方库适配实战:permission_handler 权限兼容层
2026/10/25 1:54:50 网站建设 项目流程

Flutter 3.47 鸿蒙三方库适配实战:permission_handler 权限兼容层

一、项目信息

项目内容
适配仓库名称permission_handler
适配仓库地址https://atomgit.com/oh-flutter/permission_handler
原始包名permission_handler
适配版本13.0.2-ohos.1
上游项目baseflow/flutter-permission-handler
方法通道flutter.baseflow.com/permissions/methods
OHOS 原生入口PermissionHandlerPlugin
构建产物entry-default-unsigned.hap

权限插件适配最容易出现的错误不是“请求失败”,而是“把不支持的权限伪装成已授权”。permission_handler的 OHOS 版本因此采用保守策略:只映射系统有明确对应关系的权限,未映射项统一返回denied。

环境搭建与版本

下面是本次适配实际使用的环境。环境搭建过程这里不再展开,直接看 Flutter OH 环境搭建指南。

项目版本/说明
Flutter OH3.44.9+ohos-0.0.1-canary1
Dart3.12.2
DevEco Studio26.0.0
HarmonyOS SDKAPI 26
目标仓库permission_handler

二、保留上游 Dart 契约

业务代码继续使用原来的Permission对象:

finalPermissionStatusstatus=awaitPermission.camera.status;finalPermissionStatusresult=awaitPermission.camera.request();

顶层包导出原有 API:

Future<bool>openAppSettings();abstractclassPermission{Future<PermissionStatus>getstatus;Future<PermissionStatus>request();Future<bool>getisGranted;Future<ServiceStatus>getserviceStatus;}

OHOS 适配没有要求业务页面新增PermissionOhos类型。只要宿主工程声明了对应 OHOS 权限,Dart 调用就会通过 MethodChannel 进入PermissionHandlerPlugin。

三、权限名称映射

ArkTS 插件使用permissionName()将 Dart 权限枚举转换为 OHOS 权限名:

privatepermissionName(value:number):string|null{switch(value){case1:return'ohos.permission.CAMERA';case2:return'ohos.permission.READ_CONTACTS';case3:case5:return'ohos.permission.APPROXIMATELY_LOCATION';case4:return'ohos.permission.LOCATION_IN_BACKGROUND';case7:case14:return'ohos.permission.MICROPHONE';case13:return'ohos.permission.RECEIVE_SMS';case17:return'ohos.permission.NOTIFICATION_CONTROLLER';case22:case28:case29:case30:return'ohos.permission.DISCOVER_BLUETOOTH';default:returnnull;}}

映射时不能只看权限名称相似。例如Permission.locationAlways在 OHOS 上需要后台定位权限,不能复用前台定位权限;蓝牙的扫描、连接和广播能力也要按 OHOS 权限模型分别处理。

四、请求与状态检查

状态检查使用 AccessToken:

privateasynccheckPermission(value:number):Promise<number>{constname=this.permissionName(value);if(name===null||this.context===null){return0;}conststatus=abilityAccessCtrl.createAtManager().checkAccessTokenSync(this.tokenId(),nameasPermissions,);returnstatus===abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED?1:0;}

请求权限时逐个调用系统接口:

constresponse=awaitabilityAccessCtrl.createAtManager().requestPermissionsFromUser(this.context,[nameasPermissions]);constgranted=response.authResults.length>0&&response.authResults[0]===abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;returngranted?1:0;

这里使用单权限数组是为了让每个Permission的结果保持独立,避免一个权限组的授权结果污染另一个权限。

五、宿主工程必须声明权限

OHOS 权限不能只在 Dart 层请求,宿主module.json5也必须声明。相机和麦克风示例:

"requestPermissions": [ { "name": "ohos.permission.CAMERA", "reason": "$string:camera_permission_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } }, { "name": "ohos.permission.MICROPHONE", "reason": "$string:microphone_permission_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } } ]

对应的string.json:

{ "string": [ { "name": "camera_permission_reason", "value": "用于扫描二维码和拍摄照片" }, { "name": "microphone_permission_reason", "value": "用于录制语音消息" } ] }

OHOS 工程目录创建

本次适配前,上游仓库没有ohos/目录。适配分支上按 Flutter OH 的插件工程约束创建平台目录,再把 ArkTS 入口接入pubspec.yaml:

gitswitch-c<your-ohos-adaptation-branch>flutter create--platformsohos.

工程生成后至少保留以下边界:

ohos/ ├── index.ets ├── oh-package.json5 └── src/main/ ├── module.json5 └── ets/components/plugin/

示例工程还需要生成example/ohos/,并在插件pubspec.yaml的flutter.plugin.platforms.ohos中声明pluginClass;只创建目录、没有注册入口,Dart 方法通道仍然不会工作。

六、当前不支持的能力

openAppSettings()当前明确返回false:

case'openAppSettings':result.success(false);return;

shouldShowRequestPermissionRationale()当前返回false,业务应自行在请求前解释权限用途。checkServiceStatus()返回固定的未适用状态,因为 OHOS 没有与旧接口完全一致的服务开关。

这些限制必须让业务代码可见。若把openAppSettings()返回true,调用方会以为已经跳转设置页,最终却看不到任何页面。

七、构建与模拟器验证

验证命令:

flutter pub get flutter analyzecdexample flutter pub get flutter analyze flutter build hap--debug--no-codesign

当前结果:

检查项结果
根目录flutter analyze通过
示例目录flutter analyze通过
OHOS ArkTS 编译通过
HAP 产物entry-default-unsigned.hap
release HAP 大小约 22 MB
模拟器安装启动通过
页面状态Baseflow 权限列表正常显示

适配过程 FAQ 与解决方案

为什么权限状态要通过 AccessToken 检查?
解决方案:Dart 层的枚举只是业务契约,真正的授权状态来自 OHOS AccessToken。只看请求返回值会把“已请求”误判成“已授权”。

为什么请求权限要逐个处理?
解决方案:不同权限的系统弹窗和拒绝语义不同,批量请求会让用户难以理解,也很难区分是哪一个权限导致后续功能失败。适配层应保留逐项状态。

宿主module.json5和插件 HAR 声明为什么不能混为一谈?
解决方案:插件只能声明自身需要的接口;应用是否真正请求和使用权限仍由宿主配置与运行时流程决定。缺少宿主声明时,HAR 编译通过也不代表功能可用。

用户拒绝后,如何区分永久拒绝和临时拒绝?
解决方案:先读取 AccessToken 状态,再根据系统是否还能弹窗决定是否跳转设置页。不能把所有拒绝都直接当作永久拒绝,否则用户会失去再次授权的机会。

八、权限适配的工程化验证

1. 当前映射的完整语义

OHOS 权限名不是简单按字符串替换得到的。当前适配把上游枚举值映射到系统权限:

上游枚举值OHOS 权限说明
0,36,37ohos.permission.READ_CALENDAR日历读取及上游相关变体
1ohos.permission.CAMERA相机
2ohos.permission.READ_CONTACTS联系人读取
3,5ohos.permission.APPROXIMATELY_LOCATION近似位置
4ohos.permission.LOCATION_IN_BACKGROUND后台定位
7,14ohos.permission.MICROPHONE麦克风
8ohos.permission.READ_CALL_LOG通话记录读取
9,10ohos.permission.READ_IMAGEVIDEO图片和视频读取
12ohos.permission.READ_HIVIEW_DFX系统日志读取
13ohos.permission.RECEIVE_SMS接收短信
15,23ohos.permission.READ_WRITE_USER_FILE用户文件读写
17ohos.permission.NOTIFICATION_CONTROLLER通知控制
22,28,29,30ohos.permission.DISCOVER_BLUETOOTH蓝牙发现

未出现在表中的权限不会被猜测映射,而是返回PermissionStatus.denied。这是兼容层的安全边界:宁可让业务显示“需要授权”,也不能把未知权限报告为已授权。

2. 状态检查为什么需要 AccessToken

OHOS 的权限状态不能只凭权限名判断,必须知道当前应用的 AccessToken,再交给AtManager.checkAccessTokenSync()查询:

privatetokenId():number{constinfo=bundleManager.getBundleInfoForSelfSync(0);returninfo.appInfo.accessTokenId;}privateasynccheckPermission(value:number):Promise<number>{constname=this.permissionName(value);if(name===null||this.context===null){return0;}conststatus=abilityAccessCtrl.createAtManager().checkAccessTokenSync(this.tokenId(),nameasPermissions,);returnstatus===abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED?1:0;}

这里的1和0是通道层的中间值,Dart 平台接口再把它转换为PermissionStatus.granted和PermissionStatus.denied。业务代码不需要知道这个中间编码。

3. 请求权限时为什么逐个请求

requestPermissions接收上游传来的权限枚举列表,然后逐个调用系统请求接口:

for(constpermissionofvalues){output[permission]=awaitthis.requestPermission(permission);}

逐个请求的优点是每个权限的结果独立可追踪;缺点是一次请求多个权限时可能出现多个系统弹窗。业务层如果希望一次完成授权,应该继续使用 OHOS 原生权限组能力,而不是依赖当前兼容层的串行实现。

4. 宿主权限声明与插件 HAR 权限声明不能混为一谈

插件 HAR 的module.json5只描述插件模块本身:

{ "module": { "name": "permission_handler", "type": "har", "deviceTypes": ["default", "tablet"] } }

真正请求相机、麦克风、定位等权限时,宿主应用的EntryAbility模块必须声明requestPermissions,并提供权限理由。只修改插件 HAR 的module.json5,不会让宿主应用自动获得权限。

5. 业务层如何处理“用户拒绝”

业务不应把request()的返回值直接当成成功或失败布尔值。推荐根据状态分支处理:

Future<void>ensureCameraPermission()async{PermissionStatusstatus=awaitPermission.camera.status;if(!status.isGranted){status=awaitPermission.camera.request();}if(status.isGranted){awaitopenCamera();return;}awaitshowPermissionExplanation('相机权限被拒绝,无法扫描二维码。你可以在系统设置中重新开启。',);}

由于当前 OHOS 版本的openAppSettings()返回false,不要在业务里无条件调用openAppSettings()并假设页面已经打开。应先判断返回值,再决定是否显示手动前往设置的提示。

6. 推荐的验证顺序

权限适配最容易在真实设备上出现和单元测试不同的结果,建议按以下顺序验证:

  1. 未安装权限声明时调用status,确认不会伪造授权。
  2. 在宿主module.json5中声明权限,重新构建 HAP。
  3. 首次点击request(),确认系统弹窗出现。
  4. 拒绝一次后再次请求,观察系统是否仍弹窗。
  5. 撤销权限后重新启动应用,确认状态回到denied。
  6. 对相机、麦克风、定位分别测试,不要用一个权限的结果推断另一个权限。

7. 常见故障排查

现象可能原因排查方式
所有权限都是denied宿主未声明权限或枚举未映射检查module.json5和permissionName()
权限弹窗不出现权限理由、usedScene或系统策略配置错误检查宿主配置和模拟器系统设置
tokenId()异常Bundle 信息不可用或调用时机过早在 Ability 已绑定后再请求
openAppSettings()永远false当前 OHOS 实现未提供稳定跳转能力业务显示手动设置提示
serviceStatus结果不符合预期OHOS 没有等价服务开关以notApplicable语义处理
请求多个权限只看到一个结果权限组或系统弹窗合并逐个验证并记录authResults

版本与提交基线

项目版本/地址
上游包permission_handler
上游最新版本13.0.2
文章目标基线13.0.2-ohos.1
当前已发布 TAG13.0.2-ohos.1
适配分支main
目标最新 TAG13.0.2-ohos.1
最终提交61f4aa7

版本号更新只代表文章和适配分支的基线已对齐;涉及上游 API 变更时,仍需在该 TAG 上重新运行 Dart 测试、ArkTS 编译、HAP 安装和真机回归。

九、总结

权限适配的核心是“语义一致”,而不是“方法返回成功”:

  • OHOS 权限名由 Dart 枚举显式映射。
  • 宿主module.json5负责声明权限。
  • 未映射权限返回denied,不伪造授权。
  • 设置页和 rationale 能力保持明确限制。

仓库名称:permission_handler

仓库地址:https://atomgit.com/oh-flutter/permission_handler

欢迎加入 CPF-Flutter OpenHarmony 社区,交流 Flutter 三方库适配、权限治理和虚拟机验证经验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询