KMP 结构适配鸿蒙:权限管理与用户认证理论知识
本文探讨了在Kotlin Multiplatform (KMP)框架下实现跨平台权限管理和用户认证系统的方案。文章分为两部分:第一部分阐述了权限管理的核心架构,包括权限请求、多权限处理等功能,以及用户认证系统的设计,支持多种登录方式和OAuth认证;第二部分详细介绍了平台特定实现,重点展示了Android平台上基于ActivityResult API的权限管理实现和Google OAuth登录流程
项目概述
权限管理与用户认证是现代应用开发中的核心功能。在 KMP 框架下,我们需要实现跨平台的权限请求机制和用户认证系统。本文详细介绍了如何在 Kotlin Multiplatform 中实现权限管理、OAuth 认证、以及鸿蒙系统特定的权限处理方式。
第一部分:权限管理核心概念
权限系统架构
权限管理涉及多个层面的实现:
// commonMain/kotlin/com/example/permissions/PermissionManager.kt
expect class PermissionManager {
suspend fun requestPermission(permission: String): Boolean
suspend fun requestMultiplePermissions(permissions: List<String>): Map<String, Boolean>
suspend fun hasPermission(permission: String): Boolean
suspend fun shouldShowPermissionRationale(permission: String): Boolean
}
// 权限类型定义
object Permissions {
const val CAMERA = "android.permission.CAMERA"
const val LOCATION = "android.permission.ACCESS_FINE_LOCATION"
const val CONTACTS = "android.permission.READ_CONTACTS"
const val CALENDAR = "android.permission.READ_CALENDAR"
const val MICROPHONE = "android.permission.RECORD_AUDIO"
const val STORAGE = "android.permission.READ_EXTERNAL_STORAGE"
}
认证系统架构
用户认证系统包括多种认证方式:
// commonMain/kotlin/com/example/auth/AuthManager.kt
sealed class AuthResult {
data class Success(val token: String, val user: User) : AuthResult()
data class Error(val message: String, val code: Int) : AuthResult()
object Loading : AuthResult()
}
data class User(
val id: String,
val email: String,
val name: String,
val avatar: String,
val createdAt: Long
)
expect class AuthManager {
suspend fun login(email: String, password: String): AuthResult
suspend fun register(email: String, password: String, name: String): AuthResult
suspend fun logout(): Boolean
suspend fun refreshToken(): Boolean
suspend fun getCurrentUser(): User?
suspend fun oauthLogin(provider: String): AuthResult
}
第二部分:平台特定实现
Android 权限实现
在 Android 平台上,权限管理通过 ActivityResult API 实现,这是 Android 11+ 推荐的做法。我们使用 registerForActivityResult 来处理权限请求的回调。核心思路是检查权限是否已授予,如果没有则弹出权限对话框。
// androidMain/kotlin/com/example/permissions/PermissionManager.kt
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
actual class PermissionManager(private val context: Context) {
private var permissionLauncher: ActivityResultLauncher<Array<String>>? = null
actual suspend fun requestPermission(permission: String): Boolean {
return if (ContextCompat.checkSelfPermission(
context,
permission
) == PackageManager.PERMISSION_GRANTED) {
true
} else {
requestPermissionAsync(arrayOf(permission))
}
}
actual suspend fun requestMultiplePermissions(
permissions: List<String>
): Map<String, Boolean> {
val results = mutableMapOf<String, Boolean>()
permissions.forEach { permission ->
results[permission] = requestPermission(permission)
}
return results
}
actual suspend fun hasPermission(permission: String): Boolean {
return ContextCompat.checkSelfPermission(
context,
permission
) == PackageManager.PERMISSION_GRANTED
}
actual suspend fun shouldShowPermissionRationale(permission: String): Boolean {
return ActivityCompat.shouldShowRequestPermissionRationale(
context as Activity,
permission
)
}
private suspend fun requestPermissionAsync(permissions: Array<String>): Boolean {
return suspendCancellableCoroutine { continuation ->
permissionLauncher = (context as Activity).registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { results ->
continuation.resume(results.values.all { it })
}
permissionLauncher?.launch(permissions)
}
}
}
// Android OAuth 实现
actual class AuthManager(private val context: Context) {
private val googleSignInClient = GoogleSignIn.getClient(context, GoogleSignInOptions.DEFAULT_SIGN_IN)
actual suspend fun oauthLogin(provider: String): AuthResult {
return when (provider) {
"google" -> {
try {
val task = Tasks.await(googleSignInClient.silentSignIn())
val account = task.getResult(ApiException::class.java)
val idToken = account?.idToken ?: return AuthResult.Error("No token", -1)
AuthResult.Success(idToken, User(
id = account.id ?: "",
email = account.email ?: "",
name = account.displayName ?: "",
avatar = account.photoUrl?.toString() ?: "",
createdAt = System.currentTimeMillis()
))
} catch (e: Exception) {
AuthResult.Error(e.message ?: "Google login failed", -1)
}
}
else -> AuthResult.Error("Unknown provider", -1)
}
}
}
上面的代码展示了 Android 权限管理的完整流程。requestPermission 方法首先检查权限状态,如果已授予则直接返回 true。否则,它调用 requestPermissionAsync 来显示权限对话框。这个方法使用 suspendCancellableCoroutine 将回调转换为协程,使得异步操作可以以同步的方式编写。
iOS 权限实现
iOS 的权限管理与 Android 不同,它使用特定的框架来处理不同类型的权限。例如,摄像头权限通过 AVCaptureDevice 处理,位置权限通过 CLLocationManager 处理。每种权限都有其特定的 API。
// iosMain/kotlin/com/example/permissions/PermissionManager.kt
import platform.AVFoundation.*
import platform.CoreLocation.*
import platform.Contacts.*
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
actual class PermissionManager {
actual suspend fun requestPermission(permission: String): Boolean {
return suspendCancellableCoroutine { continuation ->
when (permission) {
"camera" -> {
AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo) { granted ->
continuation.resume(granted)
}
}
"location" -> {
val locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
continuation.resume(true)
}
else -> continuation.resume(false)
}
}
}
actual suspend fun hasPermission(permission: String): Boolean {
return when (permission) {
"camera" -> AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo) == AVAuthorizationStatusAuthorized
"location" -> CLLocationManager.authorizationStatus() == kCLAuthorizationStatusAuthorizedWhenInUse
else -> false
}
}
}
iOS 的权限请求使用回调模式,通过 suspendCancellableCoroutine 将其转换为协程。这样可以在 Kotlin 中以同步的方式处理异步的权限请求。
第三部分:编译后的代码形式
JavaScript 编译版本
在 Web 平台上,权限管理通过 Permissions API 实现。这是一个标准的浏览器 API,允许网页请求特定的权限,如摄像头、麦克风等。与原生平台不同,Web 权限的粒度较粗,但基本概念是相同的。
// 权限管理器 (JavaScript/Web)
class PermissionManager {
async requestPermission(permission) {
try {
const result = await navigator.permissions.query({
name: this.mapPermission(permission)
});
return result.state === 'granted';
} catch (error) {
console.error('Permission request failed:', error);
return false;
}
}
mapPermission(permission) {
const map = {
'camera': 'camera',
'microphone': 'microphone',
'location': 'geolocation',
'storage': 'storage'
};
return map[permission] || permission;
}
async requestMultiplePermissions(permissions) {
const results = {};
for (const permission of permissions) {
results[permission] = await this.requestPermission(permission);
}
return results;
}
}
// OAuth 认证
class AuthManager {
async oauthLogin(provider) {
if (provider === 'google') {
return new Promise((resolve, reject) => {
gapi.auth2.getAuthInstance().signIn().then(
(user) => {
const profile = user.getBasicProfile();
resolve({
token: user.getAuthResponse().id_token,
user: {
id: profile.getId(),
email: profile.getEmail(),
name: profile.getName(),
avatar: profile.getImageUrl(),
createdAt: Date.now()
}
});
},
(error) => reject(error)
);
});
}
}
}
Web 版本的权限管理相对简单。mapPermission 方法将通用权限名称映射到浏览器特定的权限名称。这种抽象使得跨平台代码可以使用统一的权限名称。
第四部分:鸿蒙系统实际应用
鸿蒙权限管理
鸿蒙系统的权限管理与 Android 类似,但使用了鸿蒙特定的 API。关键区别在于权限命名空间从 android.permission 变为 ohos.permission。我们通过 mapAndroidToOhosPermission 方法将 Android 权限映射到鸿蒙权限,这样可以在共享代码中使用统一的权限名称。
// ohos/kotlin/com/example/permissions/PermissionManager.kt
import ohos.permission.PermissionManager as OhosPermissionManager
import ohos.app.Context
import ohos.bundle.IBundleManager
actual class PermissionManager(private val context: Context) {
private val permissionManager = OhosPermissionManager()
actual suspend fun requestPermission(permission: String): Boolean {
val ohosPermission = mapAndroidToOhosPermission(permission)
return if (permissionManager.canRequestPermission(context, ohosPermission)) {
permissionManager.requestPermissionsFromUser(context, arrayOf(ohosPermission), 0)
true
} else {
permissionManager.isPermissionGranted(context, ohosPermission)
}
}
actual suspend fun hasPermission(permission: String): Boolean {
val ohosPermission = mapAndroidToOhosPermission(permission)
return permissionManager.isPermissionGranted(context, ohosPermission)
}
private fun mapAndroidToOhosPermission(permission: String): String {
return when (permission) {
"android.permission.CAMERA" -> "ohos.permission.CAMERA"
"android.permission.ACCESS_FINE_LOCATION" -> "ohos.permission.LOCATION"
"android.permission.RECORD_AUDIO" -> "ohos.permission.MICROPHONE"
else -> permission.replace("android", "ohos")
}
}
}
// 鸿蒙 OAuth 实现
actual class AuthManager(private val context: Context) {
actual suspend fun oauthLogin(provider: String): AuthResult {
return when (provider) {
"huawei" -> {
try {
val accountManager = AccountManager.get(context)
val accounts = accountManager.getAccountsByType("com.huawei.hwid")
if (accounts.isNotEmpty()) {
val account = accounts[0]
val token = accountManager.blockingGetAuthToken(
account,
"oauth2:https://www.huawei.com",
false
)
AuthResult.Success(token, User(
id = account.name,
email = account.name,
name = account.name,
avatar = "",
createdAt = System.currentTimeMillis()
))
} else {
AuthResult.Error("No Huawei account", -1)
}
} catch (e: Exception) {
AuthResult.Error(e.message ?: "Huawei login failed", -1)
}
}
else -> AuthResult.Error("Unknown provider", -1)
}
}
}
鸿蒙实现展示了如何将 Android 权限映射到鸿蒙权限。这种映射方法使得我们可以在共享代码中保持一致的 API,同时在平台特定的实现中处理差异。
第五部分:高级功能
权限链式请求
在实际应用中,我们经常需要请求多个权限。权限链式请求模式允许我们以流畅的方式构建权限请求,并区分必需权限和可选权限。这样可以提供更好的用户体验——应用可以在获得必需权限后继续运行,即使可选权限被拒绝。
// commonMain/kotlin/com/example/permissions/PermissionChain.kt
class PermissionChain(private val permissionManager: PermissionManager) {
private val requiredPermissions = mutableListOf<String>()
private val optionalPermissions = mutableListOf<String>()
fun addRequired(permission: String) = apply {
requiredPermissions.add(permission)
}
fun addOptional(permission: String) = apply {
optionalPermissions.add(permission)
}
suspend fun execute(): PermissionResult {
val requiredResults = permissionManager.requestMultiplePermissions(requiredPermissions)
val optionalResults = permissionManager.requestMultiplePermissions(optionalPermissions)
val allGranted = requiredResults.values.all { it }
val partiallyGranted = optionalResults.values.any { it }
return PermissionResult(
allGranted = allGranted,
partiallyGranted = partiallyGranted,
results = requiredResults + optionalResults
)
}
}
data class PermissionResult(
val allGranted: Boolean,
val partiallyGranted: Boolean,
val results: Map<String, Boolean>
)
这个实现展示了如何使用 Builder 模式来构建权限请求。addRequired 和 addOptional 方法返回 this,允许链式调用。execute 方法分别请求必需权限和可选权限,然后返回一个结果对象,其中包含所有权限的授予状态。
令牌刷新机制
在认证系统中,访问令牌通常有过期时间。令牌刷新机制允许应用在令牌过期前自动刷新,以保持用户的登录状态。这通常通过后台定时任务实现,定期检查令牌是否需要刷新。
// commonMain/kotlin/com/example/auth/TokenRefreshManager.kt
class TokenRefreshManager(
private val authManager: AuthManager,
private val tokenStorage: TokenStorage
) {
private var refreshJob: Job? = null
fun startAutoRefresh(refreshIntervalMs: Long = 3600000) {
refreshJob = GlobalScope.launch {
while (isActive) {
delay(refreshIntervalMs)
try {
if (authManager.refreshToken()) {
val newToken = tokenStorage.getToken()
notifyTokenRefreshed(newToken)
}
} catch (e: Exception) {
notifyRefreshFailed(e)
}
}
}
}
fun stopAutoRefresh() {
refreshJob?.cancel()
}
}

总结
权限管理与用户认证是应用安全的基础。通过 KMP 框架,我们可以在共享代码中定义统一的权限和认证接口,同时在各平台上实现特定的逻辑。鸿蒙系统提供了与 Android 兼容的权限模型,使得迁移相对平顺。关键是要理解各平台的权限模型差异,并在共享代码中提供灵活的抽象层。
更多推荐



所有评论(0)