Kotlin Multiplatform for OpenHarmony 实战:为 Moko Permissions 实现运行时权限适配

大家好,我是熊猫钓鱼!欢迎大家和我一起探讨技术。希望您能点赞关注,谢谢!
摘要
本文聚焦如何在 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,模拟器即可跑弹窗与设置跳转。
目录
- 一、Moko Permissions 是什么,以及为什么要在 OpenHarmony 上适配它
- moko-permissions 核心契约(四态状态机 / PermissionsController / StateFlow 观察)
- 适配目标:把权限状态机语义还原成 ArkTS,引擎层真正接上 abilityAccessCtrl
- 二、OpenHarmony 的权限模型(适配的真实基础)
- 声明前置:
module.json5+string.json的 reason - 查询
checkAccessToken(tokenId)的数字授权码语义 - 弹窗
requestPermissionsFromUser与authResults - 撤销无公开 API;设置页跳转靠
Want
- 声明前置:
- 三、适配架构:语义层 / 引擎层 / 验收页三层
- 三层各自职责与文件落点
- 语义层只依赖自身接口,引擎层可替换
- 四、语义层:权限状态机与可观察状态(对照原库契约)
PermissionState四态 /Perm常量类PermissionStateFlow:对应 StateFlow 的 replay 1 语义PermissionController平台接口 /MokoPermissions管理器- 与原库差异:
Permission拍平成 string - 权限状态机图
- 五、引擎层:OhosPermissions 桥接 @kit.AbilityKit
check→ checkAccessToken(tokenId);provide→ requestPermissionsFromUser 弹窗(幂等)revoke跳设置;openSettings构造 Want 跳应用设置页- tokenId 与 UIAbilityContext 来源不同需分别封装
- 请求时序图
- 六、验收页:四按钮点亮权限四件套
- 检查状态 / 请求授权(真实弹窗)/ 模拟授予(免真机)/ 打开设置
- getUIContext 可能为 undefined;@State 不可变更新
- 运行时流程图
- 七、ArkTS 适配中踩到的真实坑
- enum 约束、Context 来源不同、getUIContext 判空、撤销无 API、@State 不可变更新、声明前置
- 八、关于 API 命名与类型的一点设计说明(本项目的适配决策)
- 方法命名:上游 getState/providePermission → 本项目 check/provide
Permission由 sealed 富类型拍平为 stringprovide由 suspend 无返回改为返回最终状态
- 九、版本与运行环境
- 适配平台、工具链、IDE、编译验证结果
- 十、小结与社区
- 三层架构 + 真接口真实现在接入真实系统能力的价值
- 社区引导语与 AtomCode 专属邀请链接、原创声明
在 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 适配中踩到的真实坑
enum约束:ArkTS 对enum限制较多(不支持计算值、部分场景报arkts-no-const-enum)。权限状态改用「联合类型 +Perm常量类」表达,既类型安全又零告警。- Context 来源不同:
checkAccessToken用applicationInfo.accessTokenId,requestPermissionsFromUser用UIAbilityContext本身——两个取法的对象不同,易混,故在引擎层分别封装tokenId()与构造入参。 getUIContext()可能为undefined:验收页aboutToAppear里要先判空再getHostContext(),否则页面未挂 UI 上下文时直接崩溃。- 撤销权限无公开 API:OpenHarmony 不提供「代码内撤销某权限」的能力,只能跳设置页;这点和 Android 也不同,适配时把
revoke语义落为「跳转设置」,并在日志说明。 @State数组追加:事件日志用[...this.logLines, entry]不可变更新触发 UI 刷新,直接push不会重渲染。- 权限声明是前置条件:
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
更多推荐




所有评论(0)