在这里插入图片描述

大家好,我是熊猫钓鱼!欢迎大家和我一起探讨技术。希望您能点赞关注,谢谢!

摘要

本文聚焦如何在 OpenHarmony 上为 IceRock 开源的 KMP 权限库 moko-permissions 做适配落地。moko-permissions 用一套跨平台 API(PermissionState 四态状态机 + PermissionsController 的 getState/providePermission/revokePermission/openAppSettings + StateFlow 状态观察)统一封装各平台权限请求。本文用 ArkTS 桥接 @kit.AbilityKit 的 abilityAccessCtrl,把「检查令牌 → 弹窗请求 → 回写状态」翻译成语义层的响应式状态机,全程遵循本适配工程的三层架构:语义层 Permissions.ets(PermissionState 联合类型 / PermissionStateFlow 可观察状态 / PermissionController 平台接口 / MokoPermissions 管理器,不碰 @kit.*),引擎层 OhosPermissions.ets 真正接上系统权限子系统,验收页 PermissionsDemo.ets 用四按钮点亮权限四件套,并附「模拟授予」按钮保证无真机也能截图演示。

文章第二部分专门拆解 OpenHarmony 的权限模型这一适配真实基础(module.json5 声明前置、checkAccessToken 数字授权码、requestPermissionsFromUser 弹窗、Want 跳设置页、撤销无公开 API),第三至六节逐层对照本仓库代码讲语义层、引擎层与验收页,第七节总结 ArkTS 适配踩到的 6 个真实坑,第八节对照上游库逐行讲本项目的 API 命名与类型设计决策(check/provide 命名收敛、Permission 拍平成 string、provide 改为返回最终状态)。

本适配基于 HarmonyOS SDK 6.0.0(20) + KMP&CMP 鸿蒙社区工具链 v1.1.0(Kotlin 2.2.21 / CMP 1.9.2)开发,assembleHap 编译 BUILD SUCCESSFUL、零 ArkTS error,模拟器即可跑弹窗与设置跳转。

目录

在 KMP 项目里,运行时权限是基础能力,几乎每个用到相机、麦克风、定位、日历的 App 都要先过这一关。IceRock 开源的 moko-permissions 用一套跨平台 API 把各平台的权限请求统一起来。本文聚焦如何在 OpenHarmony 上把它适配落地:用 ArkTS 桥接 @kit.AbilityKit 的 abilityAccessCtrl,把「检查令牌 → 弹窗请求 → 回写状态」翻译成语义层的响应式状态机,并遵循本适配工程一贯的三层架构(语义层 / 引擎层 / 验收页)。所有代码均来自本仓库 moko-permissions/ 目录,可直接对照阅读。

一、Moko Permissions 是什么,以及为什么要在 OpenHarmony 上适配它

moko-permissions 是 IceRock 出品的 Kotlin Multiplatform 权限库,核心契约很清晰:

  • PermissionState:四态权限状态机 —— Granted(已授权)、Denied(已拒绝)、NotDetermined(用户尚未表态)、NotAvailable(平台/设备不支持);
  • PermissionsController:平台能力接口,原库命名为 getState(返回可观察的状态流)、providePermission、revokePermission、openAppSettings;
  • 状态观察:原库以 StateFlow<PermissionState> 暴露权限状态,订阅即回灌当前值。

上游用 Kotlin expect/actual 分平台实现,Android / iOS 各自桥自己的系统 API。

在 OpenHarmony 上,权限模型是另一套独立设计(详见第二节):权限在 module.json5 里声明,运行时通过 abilityAccessCtrl 的 checkAccessToken / requestPermissionsFromUser 查询与弹窗,撤销只能到系统设置页操作。我们要做的不是另起炉灶发明一套,而是把 moko-permissions 的「权限状态机语义」原样还原成 ArkTS,并让引擎层真正接上 OpenHarmony 的权限子系统——KMP 业务层只认 moko-permissions 的契约,鸿蒙细节被隔离在引擎层之下。

二、OpenHarmony 的权限模型(适配的真实基础)

适配能不能落地,取决于我们对鸿蒙权限子系统理解得够不够透。本项目引擎层桥接的就是下面这套机制:

1. 声明是前置硬条件。 敏感权限必须在 module.json5 的 requestPermissions 数组里声明,同时要在 string.json 提供对应的 reason(弹窗时展示给用户看的用途说明)。如果只调用 requestPermissionsFromUser 而不先在 module.json5 声明,系统会直接报错「未声明」。本 demo 声明了 ohos.permission.CAMERA、ohos.permission.MICROPHONE、ohos.permission.READ_CALENDAR 三个,并各自配了 reason。

2. 查询走 checkAccessToken(tokenId, permission)。 通过 abilityAccessCtrl.createAtManager() 拿到 AtManager 后,以异步方式查询某个权限的授予情况。tokenId 取自 UIAbilityContext.applicationInfo.accessTokenId(应用的访问令牌 ID)。返回值是一个数字授权码,不是布尔:

  • 0 → PERMISSION_GRANTED(已授权);
  • -1 → PERMISSION_DENIED(被拒绝 / 未授权);
  • 其它值(如受限等)→ 本项目统一归为 NotDetermined 旁支。

3. 弹窗请求走 requestPermissionsFromUser(context, [permissions])。 第一个参数必须是 UIAbilityContext 本身(注意和上面查 tokenId 的来源对象不同)。回调结果 PermissionRequestResult.authResults 是一个数组,每个元素 0 表示允许、-1 表示拒绝。鸿蒙支持一次请求多个权限,本项目为了让「一次只看一个权限的状态」更直观,每次只传长度为 1 的数组 [permission]。

4. 撤销没有公开的程序化 API。 这点与 Android(有 revokeSelfPermissionOnKill)也不同:OpenHarmony 不提供「代码内撤销某权限」的能力,权限只能由用户到「设置 → 应用 → 权限」里手动关闭。因此本适配把 revoke 的语义落为「跳转到系统设置页」。

5. 设置页跳转靠 Want。 构造一个 Want:action 设为 'action.settings.app.info',parameters.settingsParamBundleName 设为当前应用的 bundleName(取自 context.abilityInfo.bundleName),再 context.startAbility(want) 即可打开本应用的设置详情页。

这五点是本项目引擎层真正的承重墙——下面所有代码都是围绕它们转的。

在这里插入图片描述

三、适配架构:语义层 / 引擎层 / 验收页三层

本适配工程对 KMP 三方库的鸿蒙化统一采用三层结构,Moko Permissions 这一例也不例外:

  • 语义层(moko-permissions/src/Permissions.ets):纯 ArkTS,零 @kit.*。定义状态机、可观察状态、平台接口、管理器,可被 Node 直接离线单测;
  • 引擎层(moko-permissions/src/OhosPermissions.ets):实现语义层定义的 PermissionController 接口,桥接 @kit.AbilityKit;
  • 验收页(moko-permissions/src/PermissionsDemo.ets 及 demo 工程 pages/PermissionsDemo.ets):选权限 → 检查 / 请求 / 模拟授予 / 打开设置,事件日志逐条验证。

分层的价值在于语义层只依赖自己定义的接口,引擎层是可替换的实现。将来要换一种桥接方式、或做一个假引擎跑自动化测试,语义层一行都不用动。这一隔离是本项目适配 KMP 库的通用手法,下面只围绕 Moko 这一例展开。

四、语义层:权限状态机与可观察状态(对照原库契约)

核心文件 Permissions.ets 定义了四个角色,逐一对照 moko-permissions 的原库契约:

PermissionState —— 权限状态机

四个状态与上游库一一对应:NotDetermined / Denied / Granted / NotAvailable。本项目用「联合类型 + Perm 常量类」表达(见第七节,ArkTS 下比 enum 更稳)。

PermissionStateFlow —— 可观察的状态持有者

对应原库的 StateFlow<PermissionState>:get() 取当前值,set() 仅在值变化时通知订阅者(避免无意义的重播),observe() 订阅并立即回灌当前值、返回取消订阅函数。立即回灌这一点正是对应 StateFlow 的「replay 1」语义,是把权限事件翻译成响应式流的关键。

PermissionController —— 平台能力接口

check / provide / revoke / openSettings 四个方法,由引擎层实现。语义层只依赖这个接口,完全不关心底层是 Android Context 还是鸿蒙 UIAbilityContext。

MokoPermissions —— 管理器

内部用 Map<string, PermissionStateFlow> 按权限字符串维护状态流,把动作委托给 controller:check 查完刷新状态流、provide 请求完刷新、revoke 把对应状态置回 NotDetermined、bind 暴露订阅入口。上层业务只跟 MokoPermissions 这一个门面打交道。

与原库的一处有意差异: 原库 Permission 是一个带类型安全的 sealed 富类型(如 Permission.CAMERA、Permission.RECORD_AUDIO 等对象),而本项目把 Permission 简化成了 string。原因在于 OpenHarmony 的权限标识本身就是字符串("ohos.permission.CAMERA"),string 能直接对上系统 API,也省掉了 sealed + expect/actual 的复杂度——这是为鸿蒙场景做的合理收敛,详见第八节。

在这里插入图片描述

五、引擎层:OhosPermissions 桥接 @kit.AbilityKit

OhosPermissions.ets 实现 PermissionController,把语义层的四个动作翻译成第二节里的系统调用:

  • check(permission):atManager.checkAccessToken(this.tokenId(), permission as Permissions)。tokenId() 是引擎层封装的私有方法,返回 context.applicationInfo.accessTokenId。返回码 0 映射 Granted、-1 映射 Denied、其它映射 NotDetermined。注意 permission 在语义层是 string,而系统的 Permissions 是更窄的联合类型,这里需要 as Permissions 强转(ArkTS 严格类型,见第七节)。
  • provide(permission):先 check,已 Granted 就直接返回(幂等,避免重复弹窗);否则 atManager.requestPermissionsFromUser(this.context, [permission as Permissions]) 弹窗,把结果 as PermissionRequestResult 后读 authResults[0](0 授予 / -1 拒绝 → 对应状态)。catch 到异常一律归为 Denied(用户取消或系统异常都按拒绝处理,便于上层安全降级)。
  • revoke(permission):OpenHarmony 无公开撤销 API,直接调用 openSettings()(见第二节模型说明)。
  • openSettings():取 context.abilityInfo.bundleName,构造 Want(action: 'action.settings.app.info' + settingsParamBundleName: bundleName),context.startAbility(want) 打开本应用设置页。

关键细节:checkAccessToken 只需 tokenId,而 requestPermissionsFromUser 必须传 UIAbilityContext 本身——两者来源对象不同,引擎层分别封装(tokenId() 私有方法 vs 构造时保存的 context 字段),从根上避免混淆。

在这里插入图片描述
开发界面如下:
在这里插入图片描述
完成编译如下:
在这里插入图片描述

六、验收页:四按钮点亮权限四件套

PermissionsDemo.ets 用四个按钮把 moko-permissions 的「check / provide / revoke / openSettings」全部点亮(本项目命名,见第八节):

  • 检查状态 check(不弹窗):调 mgr.check(selected),日志打印 check(ohos.permission.CAMERA) = Granted/Denied/NotDetermined;
  • 请求授权 provide(真实弹窗):调 mgr.provide(selected),系统弹出权限请求框,允许/拒绝后日志打印 provide 返回 = Granted/Denied;
  • ▶ 模拟授予(免真机截图):直接把状态置为 Granted 并打印提示——这是 demo 专用便利,不触碰任何系统 API,模拟器无真机也能演示「授权成功」效果,便于截屏;
  • 打开系统设置(撤销授权):调 mgr.openSettings() 跳设置页,真实撤销授权在此操作。

进页 aboutToAppear 里先用 getUIContext().getHostContext() 取 UIAbilityContext,该方法可能为 undefined,必须先判空再构造引擎,否则页面还没挂上 UI 上下文时会直接崩。样例权限三个(CAMERA / MICROPHONE / READ_CALENDAR),选中哪个就自动 check 刷新状态。事件日志用 [...this.logLines, entry] 的不可变追加来触发 UI 刷新——直接 push 不会重渲染。

在这里插入图片描述
真实运行效果如下:
在这里插入图片描述
点击请求授权按钮将会自动弹窗。
在这里插入图片描述
在这里插入图片描述
点击允许后,发现已完成授权。其他场景授权功能类似,就不一一演示了。
在这里插入图片描述

七、ArkTS 适配中踩到的真实坑

  1. enum 约束:ArkTS 对 enum 限制较多(不支持计算值、部分场景报 arkts-no-const-enum)。权限状态改用「联合类型 + Perm 常量类」表达,既类型安全又零告警。
  2. Context 来源不同:checkAccessToken 用 applicationInfo.accessTokenId,requestPermissionsFromUser 用 UIAbilityContext 本身——两个取法的对象不同,易混,故在引擎层分别封装 tokenId() 与构造入参。
  3. getUIContext() 可能为 undefined:验收页 aboutToAppear 里要先判空再 getHostContext(),否则页面未挂 UI 上下文时直接崩溃。
  4. 撤销权限无公开 API:OpenHarmony 不提供「代码内撤销某权限」的能力,只能跳设置页;这点和 Android 也不同,适配时把 revoke 语义落为「跳转设置」,并在日志说明。
  5. @State 数组追加:事件日志用 [...this.logLines, entry] 不可变更新触发 UI 刷新,直接 push 不会重渲染。
  6. 权限声明是前置条件:module.json5 的 requestPermissions 必须声明 ohos.permission.CAMERA 等,且 string.json 要有对应 reason,否则 requestPermissionsFromUser 直接报「未声明」。

八、关于 API 命名与类型的一点设计说明(本项目的适配决策)

这一节是真正把本项目代码和上游库逐行比对后才有的结论,不是泛泛而谈:

  • 方法命名:上游是 getState / providePermission / revokePermission / openAppSettings;本项目收敛为 check / provide / revoke / openSettings。理由有三:getState 在 ArkTS 里易与 getter 语义混淆;provide / revoke 更短,与「授予 / 撤销」动作直觉一致;openSettings 比 openAppSettings 更直观。这是语义层对外契约的自由,不影响上游 Kotlin 侧真实库的 API 形态。
  • Permission 类型扁平化:上游是 sealed 富类型(每个权限是一个类型安全的对象),本项目把它拍平成 string。因为鸿蒙的权限标识本就是字符串,且引擎层桥接的系统 API(checkAccessToken / requestPermissionsFromUser)接收的也是字符串,扁平后在两层之间零转换;代价是失去了编译期权限类型校验,但换来与系统 API 的直接对齐。
  • provide 的返回值:上游 providePermission 是 suspend 无返回,结果通过 StateFlow 观察;本项目让 provide 直接 return 最终的 PermissionState,便于验收页同步刷新 this.state,是把「异步结果从流改为返回值」的便利取舍。

这些决策都围绕一个目标:让语义层契约在 ArkTS 里读着顺、写着短,同时引擎层能无缝对上 OpenHarmony 的系统 API。

九、版本与运行环境

  • 适配目标平台:HarmonyOS SDK 6.0.0(20)(API 20)
  • 工具链:KMP&CMP 鸿蒙社区工具链 v1.1.0(Kotlin 2.2.21 / CMP 1.9.2)
  • IDE:DevEco Studio 26.0.0 Release
  • 编译验证:本适配所在 demo 工程执行 assembleHap → BUILD SUCCESSFUL,零 ArkTS error

十、小结与社区

Moko Permissions 的适配,核心价值在于语义层用 PermissionState / PermissionStateFlow / PermissionController / MokoPermissions 把权限状态机讲清楚,引擎层用 OhosPermissions 真正接上 @kit.AbilityKit 的 abilityAccessCtrl——零自建权限逻辑、纯桥接系统 API。验收页四个按钮点亮 check / provide / revoke / openSettings 全链路,模拟器就能跑弹窗与设置跳转,外加「模拟授予」按钮保证无真机也能截图演示。对 KMP 业务而言,它拿到的是一套与 Android / iOS 同形的权限契约,鸿蒙差异被完全挡在引擎层之下。

欢迎加入 KMP&CMP 鸿蒙社区:https://atomgit.com/CPF-KMP-CMP
AtomCode 专属邀请链接:
https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwatomgit1&sourcead=dmzntgwatomgiths

Logo

一站式 AI 云服务平台

更多推荐