Kotlin Multiplatform (KMP) 中,如何实现跨平台权限请求?
在 Kotlin Multiplatform 中实现跨平台权限请求,最主流的方式是使用社区维护的第三方库,它们封装了底层的平台差异,能让你在共享代码中通过统一的 API 来请求权限。当然,也可以完全使用 Kotlin 原生的 expect/actual 机制自行封装。
目前,最常用的两个库是 kPermissions 和 moko-permissions。
| 特性 | kPermissions | moko-permissions |
|---|---|---|
| 特点 | API 设计现代、轻量,无需使用 Flow 或委托,支持声明式权限请求。 | 历史悠久、社区成熟,功能强大,提供全面的权限类型和细致的异常处理。 |
| Compose 支持 | 原生支持,提供 rememberPermissionState 等 Compose 便捷方法。 | 提供专门的 permissions-compose 模块,与 Compose 无缝集成。 |
| 模块化 | 提供核心 API 和一系列功能模块,可按需添加。 | 同样采用模块化设计,可按需引入特定权限模块。 |
| 优点 | API 直观,特别适合 Compose 项目,上手快。 | 生态更成熟,支持权限类型丰富,异常处理机制完善。 |
| 缺点 | 相对较新,社区规模和成熟度略逊于 moko。 | 部分开发者认为其 API 稍显厚重。 |
对于 Compose Multiplatform 项目,kPermissions 的声明式 API 更契合现代 UI 开发模式。而如果你的项目对权限的精细控制要求很高,moko-permissions 会是更稳妥的选择。
🛠️ 两种主流实现方式
以下将分别介绍如何使用 kPermissions、moko-permissions 以及 expect/actual 机制来实现跨平台权限请求。
📦 方式一:使用 kPermissions
kPermissions 库为 KMP + Compose 项目提供了非常便利的 API。
-
添加依赖 在你的
shared模块的build.gradle.kts文件中,添加所需权限模块的依赖。// shared/build.gradle.kts kotlin { sourceSets { val commonMain by getting { dependencies { // 例如,添加相机和图库权限 implementation("io.github.kpermissions:kpermissions-camera:1.0.1") implementation("io.github.kpermissions:kpermissions-gallery:1.0.1") } } } } -
使用 Compose API 请求权限 在共享 UI 中,你可以使用
rememberPermissionState和LaunchedEffect以声明式方式触发权限请求。@Composable fun CameraScreen() { // 1. 创建权限状态 val cameraPermission = rememberPermissionState(CameraPermission) // 2. 在副作用中触发权限请求 LaunchedEffect(Unit) { cameraPermission.launchPermissionRequest { granted -> if (granted) { // 权限已授予,可以打开相机 } else { // 权限被拒绝,可以展示说明或引导用户去设置中开启 } } } // UI 界面 // ... }
📦 方式二:使用 moko-permissions
moko-permissions 功能全面,同样支持 Compose,其 API 与 kPermissions 有所不同。
-
添加依赖 在你的
shared模块的build.gradle.kts文件中添加核心依赖和 Compose 支持。// shared/build.gradle.kts kotlin { sourceSets { val commonMain by getting { dependencies { // 核心权限 API implementation("dev.icerock.moko:permissions:0.20.1") // 权限模块,例如相机、位置等 implementation("dev.icerock.moko:permissions-camera:0.20.1") implementation("dev.icerock.moko:permissions-location:0.20.1") // 为 Compose Multiplatform 提供扩展 implementation("dev.icerock.moko:permissions-compose:0.20.1") } } } } -
使用
PermissionsController请求权限 moko-permissions 的核心是PermissionsController。你需要在共享的ViewModel或Presenter中持有它的实例。// 在共享的 ViewModel 中 class MyViewModel(private val permissionsController: PermissionsController) { fun requestCameraPermission() { permissionsController.providePermission(Permission.CAMERA) { result -> when (result) { is PermissionResult.Granted -> { // 权限已授予 } is PermissionResult.Denied -> { // 权限被拒绝(非永久) // 可以在此展示申请权限的缘由 } is PermissionResult.DeniedAlways -> { // 权限被永久拒绝 // 需要引导用户去系统设置中手动开启 permissionsController.openAppSettings() } } } } }
🧱 方式三:使用 expect/actual 自行实现
如果不想引入第三方库,完全可以使用 Kotlin 的 expect/actual 机制来手动封装平台特定的权限 API。
-
在
commonMain中定义expect接口和状态 首先,定义通用的权限状态枚举和一个expect的权限处理器接口。// commonMain 代码 enum class PermissionStatus { GRANTED, DENIED, PERMANENTLY_DENIED } expect class PlatformPermissionHandler() { suspend fun requestPermission(permission: String): PermissionStatus fun checkPermission(permission: String): PermissionStatus } -
在
androidMain和iosMain中提供actual实现 然后,分别针对 Android 和 iOS 平台提供这个接口的具体实现。-
Android 实现 (
androidMain)// androidMain 代码 actual class PlatformPermissionHandler( private val activity: Activity ) { actual suspend fun requestPermission(permission: String): PermissionStatus { // 使用 Android 的 requestPermissions API 进行请求 // ... } // ... } -
iOS 实现 (
iosMain)// iosMain 代码 actual class PlatformPermissionHandler { actual suspend fun requestPermission(permission: String): PermissionStatus { return when (permission) { "camera" -> requestCameraPermission() // ... else -> PermissionStatus.DENIED } } private suspend fun requestCameraPermission(): PermissionStatus { return suspendCancellableCoroutine { continuation -> AVCaptureDevice.requestAccess(for: .video) { granted -> continuation.resume(if (granted) PermissionStatus.GRANTED else PermissionStatus.DENIED) } } } // ... }
-
📄 平台配置清单
在使用任何权限之前,必须确保在各个平台的工程中进行了正确的配置。这份清单务必逐项检查。
| 步骤 | Android | iOS |
|---|---|---|
| 1. 声明权限 | 在 AndroidManifest.xml 文件中添加 <uses-permission> 标签。例如:<uses-permission android:name="android.permission.CAMERA" />。 | 在 Info.plist 文件中添加对应的 Usage Description 键值对。例如:<key>NSCameraUsageDescription</key><string>我们需要访问相机来拍照</string>。 |
| 2. 请求时机 | 在运行时请求”危险”权限(如相机、位置、存储)。 | 权限请求均在运行时触发,系统会自动显示提示对话框。 |
| 3. 处理拒绝 | 用户拒绝后,可再次请求,或通过 shouldShowRequestPermissionRationale 判断是否需要解释,最终可引导至系统设置开启。 | 用户拒绝后,再次请求时系统通常不会弹出提示框。通常需要直接引导用户到系统设置中手动开启。 |
💎 总结与建议
- 项目起步:如果你的项目使用了 Compose Multiplatform,建议从 kPermissions 开始,它的 API 非常现代且与 Compose 结合紧密。
- 成熟稳定:对于追求稳定性和功能全面性的项目,moko-permissions 是久经考验的可靠选择。
- 极致控制:若希望完全掌控权限逻辑或避免引入第三方库,使用
expect/actual机制手动实现也是一个完全可行的方案。