Android Kotlin权限管理最佳实践

在 Android 开发中,权限管理是保证应用安全性和用户体验的重要环节。以下是基于 Kotlin 的权限管理封装最佳实践,采用现代 API 和简洁的代码结构:


一、基础封装思路

  1. 使用 Activity Result API (替代 onRequestPermissionsResult
  2. 结合 Kotlin 扩展函数和高阶函数简化调用
  3. 统一处理权限请求结果(包括拒绝/永久拒绝)
  4. 支持多权限请求和单权限请求

二、核心封装代码

kotlin 复制代码
// PermissionManager.kt
object PermissionManager {

    // region ========== Contracts ==========
    private val multiplePermissionsContract =
        ActivityResultContracts.RequestMultiplePermissions()

    private val singlePermissionContract =
        ActivityResultContracts.RequestPermission()
    // endregion

    // region ========== Result Types ==========
    sealed class PermissionResult {
        object Granted : PermissionResult()
        data class Denied(val permissions: List<String>) : PermissionResult()
        data class PermanentlyDenied(val permissions: List<String>) : PermissionResult()
    }
    // endregion

    // region ========== Public Methods ==========
    fun checkPermissions(
        lifecycleOwner: LifecycleOwner,
        registry: ActivityResultRegistry,
        permissions: List<String>,
        onResult: (PermissionResult) -> Unit
    ) {
        when {
            // 已有全部权限
            permissions.all { isGranted(it) } -> {
                onResult(PermissionResult.Granted)
            }
            // 需要请求权限
            else -> launchPermissionRequest(
                lifecycleOwner,
                registry,
                permissions,
                onResult
            )
        }
    }
    // endregion

    // region ========== Private Methods ==========
    private fun launchPermissionRequest(
        lifecycleOwner: LifecycleOwner,
        registry: ActivityResultRegistry,
        permissions: List<String>,
        onResult: (PermissionResult) -> Unit
    ) {
        val launcher = registry.register(
            "permission_request_key",
            lifecycleOwner,
            if (permissions.size > 1) multiplePermissionsContract
            else singlePermissionContract
        ) { results ->
            handlePermissionResult(results, permissions, onResult)
        }

        launcher.launch(permissions.toTypedArray())
    }

    private fun handlePermissionResult(
        results: Map<String, Boolean>,
        requestedPermissions: List<String>,
        onResult: (PermissionResult) -> Unit
    ) {
        val deniedPermissions = results.filter { !it.value }.keys.toList()
        val permanentlyDenied = deniedPermissions.filter { !shouldShowRequestPermissionRationale(it) }

        when {
            deniedPermissions.isEmpty() -> onResult(PermissionResult.Granted)
            permanentlyDenied.isNotEmpty() -> onResult(PermissionResult.PermanentlyDenied(permanentlyDenied))
            else -> onResult(PermissionResult.Denied(deniedPermissions))
        }
    }

    private fun isGranted(permission: String): Boolean {
        return ContextCompat.checkSelfPermission(
            ApplicationProvider.getApplicationContext(),
            permission
        ) == PackageManager.PERMISSION_GRANTED
    }

    private fun shouldShowRequestPermissionRationale(permission: String): Boolean {
        val activity = getTopActivity() ?: return false
        return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)
    }
    // endregion
}

三、使用扩展函数简化调用

kotlin 复制代码
// Activity/Fragment Extensions
fun Fragment.requestPermissions(
    permissions: List<String>,
    onResult: (PermissionManager.PermissionResult) -> Unit
) {
    PermissionManager.checkPermissions(
        viewLifecycleOwner,
        requireActivity().activityResultRegistry,
        permissions,
        onResult
    )
}

fun Activity.requestPermissions(
    permissions: List<String>,
    onResult: (PermissionManager.PermissionResult) -> Unit
) {
    PermissionManager.checkPermissions(
        this,
        activityResultRegistry,
        permissions,
        onResult
    )
}

四、业务层调用示例

kotlin 复制代码
// 在 Activity/Fragment 中调用
requestPermissions(listOf(
    Manifest.permission.CAMERA,
    Manifest.permission.READ_EXTERNAL_STORAGE
)) { result ->
    when (result) {
        is PermissionManager.PermissionResult.Granted -> {
            // 权限已授予
            openCamera()
        }
        is PermissionManager.PermissionResult.Denied -> {
            // 部分权限被拒绝(非永久)
            showRationaleDialog()
        }
        is PermissionManager.PermissionResult.PermanentlyDenied -> {
            // 权限被永久拒绝
            showSettingsRedirectDialog()
        }
    }
}

五、最佳实践要点

  1. 分层处理逻辑

    • UI 层:处理弹窗提示和用户交互
    • 业务层:处理权限相关业务逻辑
    • 框架层:处理系统 API 交互
  2. Rationale 处理策略

kotlin 复制代码
private fun showRationaleDialog() {
    MaterialAlertDialogBuilder(requireContext())
        .setTitle("需要权限")
        .setMessage("此功能需要相机和存储权限")
        .setPositiveButton("去授权") { _, _ ->
            // 再次请求权限
            requestPermissions(permissionsList, callback)
        }
        .setNegativeButton("取消", null)
        .show()
}
  1. 永久拒绝处理
kotlin 复制代码
private fun showSettingsRedirectDialog() {
    MaterialAlertDialogBuilder(requireContext())
        .setTitle("权限被永久拒绝")
        .setMessage("请到设置中手动开启权限")
        .setPositiveButton("去设置") { _, _ ->
            Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
                data = Uri.fromParts("package", requireContext().packageName, null)
                startActivity(this)
            }
        }
        .setNegativeButton("取消", null)
        .show()
}

六、注意事项

  1. 清单声明 :仍需在 AndroidManifest.xml 中声明权限
  2. 结果回调:通过 Activity Result API 自动处理生命周期
  3. 配置变化:系统自动处理屏幕旋转等配置变化
  4. 权限分组:实际仍需单独检查每个权限状态

通过这种封装方式,可以实现以下优势:

  • 类型安全的权限结果处理
  • 与生命周期自动绑定
  • 支持 Fragment 和 Activity 统一调用
  • 清晰的拒绝状态区分
  • 避免内存泄漏风险

建议根据项目需求进一步扩展功能(如日志记录、Analytics 埋点等)。

相关推荐
neoooo2 分钟前
别慌,Java只有值传递——一次搞懂“为啥我改了它还不变”!
java·后端·spring
秋难降3 分钟前
Python 知识 “八股”:给有 C 和 Java 基础的你😁😁😁
java·python·c
wuxuanok5 分钟前
Web后端开发-请求响应
java·开发语言·笔记·学习
livemetee13 分钟前
spring-ai 1.0.0 (3)交互增强:Advisor 顾问模块
java
DDDDDouble19 分钟前
<二>Sping-AI alibaba 入门-记忆聊天及持久化
java·人工智能
一切顺势而行37 分钟前
kafka总结
java
yanjiaweiya1 小时前
云原生-集群管理
java·开发语言·云原生
gadiaola1 小时前
【JavaSE面试篇】Java集合部分高频八股汇总
java·面试
艾迪的技术之路2 小时前
redisson使用lock导致死锁问题
java·后端·面试