Android Kotlin权限管理最佳实践
在 Android 开发中,权限管理是保证应用安全性和用户体验的重要环节。以下是基于 Kotlin 的权限管理封装最佳实践,采用现代 API 和简洁的代码结构:
一、基础封装思路
- 使用 Activity Result API(替代
onRequestPermissionsResult
) - 结合 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) multiplePermissionsContractelse 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 falsereturn ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)}// endregion
}
三、使用扩展函数简化调用
// 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)
}
四、业务层调用示例
// 在 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()}}
}
五、最佳实践要点
-
分层处理逻辑
- UI 层:处理弹窗提示和用户交互
- 业务层:处理权限相关业务逻辑
- 框架层:处理系统 API 交互
-
Rationale 处理策略
private fun showRationaleDialog() {MaterialAlertDialogBuilder(requireContext()).setTitle("需要权限").setMessage("此功能需要相机和存储权限").setPositiveButton("去授权") { _, _ ->// 再次请求权限requestPermissions(permissionsList, callback)}.setNegativeButton("取消", null).show()
}
- 永久拒绝处理
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()
}
六、注意事项
- 清单声明:仍需在
AndroidManifest.xml
中声明权限 - 结果回调:通过 Activity Result API 自动处理生命周期
- 配置变化:系统自动处理屏幕旋转等配置变化
- 权限分组:实际仍需单独检查每个权限状态
通过这种封装方式,可以实现以下优势:
- 类型安全的权限结果处理
- 与生命周期自动绑定
- 支持 Fragment 和 Activity 统一调用
- 清晰的拒绝状态区分
- 避免内存泄漏风险
建议根据项目需求进一步扩展功能(如日志记录、Analytics 埋点等)。