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