Flutter 3.47 鸿蒙三方库适配实战:permission_handler 权限兼容层
Flutter 3.47 鸿蒙三方库适配实战:permission_handler 权限兼容层
一、项目信息
| 项目 | 内容 |
|---|---|
| 适配仓库名称 | 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。
二、保留上游 Dart 契约
业务代码继续使用原来的 Permission 对象:
final PermissionStatus status = await Permission.camera.status;
final PermissionStatus result = await Permission.camera.request();
顶层包导出原有 API:
Future<bool> openAppSettings();
abstract class Permission {
Future<PermissionStatus> get status;
Future<PermissionStatus> request();
Future<bool> get isGranted;
Future<ServiceStatus> get serviceStatus;
}
OHOS 适配没有要求业务页面新增 PermissionOhos 类型。只要宿主工程声明了对应 OHOS 权限,Dart 调用就会通过 MethodChannel 进入 PermissionHandlerPlugin。
三、权限名称映射
ArkTS 插件使用 permissionName() 将 Dart 权限枚举转换为 OHOS 权限名:
private permissionName(value: number): string | null {
switch (value) {
case 1:
return 'ohos.permission.CAMERA';
case 2:
return 'ohos.permission.READ_CONTACTS';
case 3:
case 5:
return 'ohos.permission.APPROXIMATELY_LOCATION';
case 4:
return 'ohos.permission.LOCATION_IN_BACKGROUND';
case 7:
case 14:
return 'ohos.permission.MICROPHONE';
case 13:
return 'ohos.permission.RECEIVE_SMS';
case 17:
return 'ohos.permission.NOTIFICATION_CONTROLLER';
case 22:
case 28:
case 29:
case 30:
return 'ohos.permission.DISCOVER_BLUETOOTH';
default:
return null;
}
}
映射时不能只看权限名称相似。例如 Permission.locationAlways 在 OHOS 上需要后台定位权限,不能复用前台定位权限;蓝牙的扫描、连接和广播能力也要按 OHOS 权限模型分别处理。
四、请求与状态检查
状态检查使用 AccessToken:
private async checkPermission(value: number): Promise<number> {
const name = this.permissionName(value);
if (name === null || this.context === null) {
return 0;
}
const status = abilityAccessCtrl.createAtManager().checkAccessTokenSync(
this.tokenId(),
name as Permissions,
);
return status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED ? 1 : 0;
}
请求权限时逐个调用系统接口:
const response = await abilityAccessCtrl.createAtManager()
.requestPermissionsFromUser(this.context, [name as Permissions]);
const granted = response.authResults.length > 0 &&
response.authResults[0] ===
abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
return granted ? 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": "用于录制语音消息"
}
]
}
六、当前不支持的能力
openAppSettings() 当前明确返回 false:
case 'openAppSettings':
result.success(false);
return;
shouldShowRequestPermissionRationale() 当前返回 false,业务应自行在请求前解释权限用途。checkServiceStatus() 返回固定的未适用状态,因为 OHOS 没有与旧接口完全一致的服务开关。
这些限制必须让业务代码可见。若把 openAppSettings() 返回 true,调用方会以为已经跳转设置页,最终却看不到任何页面。
七、构建与模拟器验证
验证命令:
flutter pub get
flutter analyze
cd example
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 权限列表正常显示 |
模拟器截图:

八、权限适配的工程化验证
1. 当前映射的完整语义
OHOS 权限名不是简单按字符串替换得到的。当前适配把上游枚举值映射到系统权限:
| 上游枚举值 | OHOS 权限 | 说明 |
|---|---|---|
0, 36, 37 | ohos.permission.READ_CALENDAR | 日历读取及上游相关变体 |
1 | ohos.permission.CAMERA | 相机 |
2 | ohos.permission.READ_CONTACTS | 联系人读取 |
3, 5 | ohos.permission.APPROXIMATELY_LOCATION | 近似位置 |
4 | ohos.permission.LOCATION_IN_BACKGROUND | 后台定位 |
7, 14 | ohos.permission.MICROPHONE | 麦克风 |
8 | ohos.permission.READ_CALL_LOG | 通话记录读取 |
9, 10 | ohos.permission.READ_IMAGEVIDEO | 图片和视频读取 |
12 | ohos.permission.READ_HIVIEW_DFX | 系统日志读取 |
13 | ohos.permission.RECEIVE_SMS | 接收短信 |
15, 23 | ohos.permission.READ_WRITE_USER_FILE | 用户文件读写 |
17 | ohos.permission.NOTIFICATION_CONTROLLER | 通知控制 |
22, 28, 29, 30 | ohos.permission.DISCOVER_BLUETOOTH | 蓝牙发现 |
未出现在表中的权限不会被猜测映射,而是返回 PermissionStatus.denied。这是兼容层的安全边界:宁可让业务显示“需要授权”,也不能把未知权限报告为已授权。
2. 状态检查为什么需要 AccessToken
OHOS 的权限状态不能只凭权限名判断,必须知道当前应用的 AccessToken,再交给 AtManager.checkAccessTokenSync() 查询:
private tokenId(): number {
const info = bundleManager.getBundleInfoForSelfSync(0);
return info.appInfo.accessTokenId;
}
private async checkPermission(value: number): Promise<number> {
const name = this.permissionName(value);
if (name === null || this.context === null) {
return 0;
}
const status = abilityAccessCtrl.createAtManager().checkAccessTokenSync(
this.tokenId(),
name as Permissions,
);
return status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
? 1
: 0;
}
这里的 1 和 0 是通道层的中间值,Dart 平台接口再把它转换为 PermissionStatus.granted 和 PermissionStatus.denied。业务代码不需要知道这个中间编码。
3. 请求权限时为什么逐个请求
requestPermissions 接收上游传来的权限枚举列表,然后逐个调用系统请求接口:
for (const permission of values) {
output[permission] = await this.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 {
PermissionStatus status = await Permission.camera.status;
if (!status.isGranted) {
status = await Permission.camera.request();
}
if (status.isGranted) {
await openCamera();
return;
}
await showPermissionExplanation(
'相机权限被拒绝,无法扫描二维码。你可以在系统设置中重新开启。',
);
}
由于当前 OHOS 版本的 openAppSettings() 返回 false,不要在业务里无条件调用 openAppSettings() 并假设页面已经打开。应先判断返回值,再决定是否显示手动前往设置的提示。
6. 推荐的验证顺序
权限适配最容易在真实设备上出现和单元测试不同的结果,建议按以下顺序验证:
- 未安装权限声明时调用
status,确认不会伪造授权。 - 在宿主
module.json5中声明权限,重新构建 HAP。 - 首次点击
request(),确认系统弹窗出现。 - 拒绝一次后再次请求,观察系统是否仍弹窗。
- 撤销权限后重新启动应用,确认状态回到
denied。 - 对相机、麦克风、定位分别测试,不要用一个权限的结果推断另一个权限。
7. 常见故障排查
| 现象 | 可能原因 | 排查方式 |
|---|---|---|
所有权限都是 denied | 宿主未声明权限或枚举未映射 | 检查 module.json5 和 permissionName() |
| 权限弹窗不出现 | 权限理由、usedScene 或系统策略配置错误 | 检查宿主配置和模拟器系统设置 |
tokenId() 异常 | Bundle 信息不可用或调用时机过早 | 在 Ability 已绑定后再请求 |
openAppSettings() 永远 false | 当前 OHOS 实现未提供稳定跳转能力 | 业务显示手动设置提示 |
serviceStatus 结果不符合预期 | OHOS 没有等价服务开关 | 以 notApplicable 语义处理 |
| 请求多个权限只看到一个结果 | 权限组或系统弹窗合并 | 逐个验证并记录 authResults |
九、总结
权限适配的核心是“语义一致”,而不是“方法返回成功”:
- OHOS 权限名由 Dart 枚举显式映射。
- 宿主
module.json5负责声明权限。 - 未映射权限返回
denied,不伪造授权。 - 设置页和 rationale 能力保持明确限制。
欢迎加入 CPF-Flutter OpenHarmony 社区,交流 Flutter 三方库适配、权限治理和虚拟机验证经验。
更多推荐




所有评论(0)