Skip to content

一、运行时权限的背景与分类

1. 为什么需要运行时权限?

  • 用户隐私保护:防止应用在用户不知情时滥用权限(如获取通讯录、定位等)。
  • 最小权限原则:按需申请权限,减少不必要的权限请求。
  • 透明化操作:用户可随时在系统设置中调整权限状态。

2. 权限分类

  • 普通权限(Normal):不涉及隐私,系统自动授予(如网络访问 INTERNET)。
  • 危险权限(Dangerous):涉及隐私,需运行时申请(如 CAMERAREAD_CONTACTS)。
  • 特殊权限(Special):需用户手动在系统设置中开启(如悬浮窗 SYSTEM_ALERT_WINDOW)。

常见危险权限组

权限组包含权限示例
CALENDARREAD_CALENDAR, WRITE_CALENDAR
CAMERACAMERA
CONTACTSREAD_CONTACTS, WRITE_CONTACTS
LOCATIONACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION
MICROPHONERECORD_AUDIO
STORAGEREAD_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE

二、运行时权限请求流程

1. 检查权限状态

使用 checkSelfPermission() 判断是否已授权:

kotlin
val permissionStatus = ContextCompat.checkSelfPermission(
    context, 
    Manifest.permission.ACCESS_FINE_LOCATION
)
when (permissionStatus) {
    PackageManager.PERMISSION_GRANTED -> { /* 已授权 */ }
    PackageManager.PERMISSION_DENIED -> { /* 未授权 */ }
}

2. 请求权限

  • 传统方式:通过 requestPermissions() 请求。
  • 推荐方式:使用 Activity Result API(更简洁,支持协程)。

示例(Activity Result API)

kotlin
// 初始化权限请求启动器
val requestPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted ->
    if (isGranted) {
        // 权限已授予
        openCamera()
    } else {
        // 处理拒绝逻辑
        showPermissionDeniedDialog()
    }
}

// 触发权限请求
requestPermissionLauncher.launch(Manifest.permission.CAMERA)

3. 处理多权限请求

kotlin
val requestMultiplePermissionsLauncher = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
    permissions.entries.forEach { (permission, isGranted) ->
        when {
            isGranted -> { /* 单个权限已授予 */ }
            else -> { /* 处理拒绝 */ }
        }
    }
}

// 请求多个权限
requestMultiplePermissionsLauncher.launch(
    arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.READ_CONTACTS
    )
)

三、处理用户拒绝权限

1. 解释权限必要性(Rationale)

当用户首次拒绝权限时,可通过 shouldShowRequestPermissionRationale() 判断是否需要展示解释:

kotlin
if (ActivityCompat.shouldShowRequestPermissionRationale(
        activity,
        Manifest.permission.CAMERA
    )) {
    // 显示解释对话框
    AlertDialog.Builder(this)
        .setTitle("需要相机权限")
        .setMessage("此功能需要访问相机以拍摄照片。")
        .setPositiveButton("允许") { _, _ ->
            requestPermissionLauncher.launch(Manifest.permission.CAMERA)
        }
        .setNegativeButton("取消", null)
        .show()
} else {
    // 直接请求权限或引导用户到设置
    requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}

2. 处理“永久拒绝”情况

用户勾选“不再询问”后,shouldShowRequestPermissionRationale() 返回 false,需引导用户手动开启权限:

kotlin
fun openAppSettings() {
    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
        data = Uri.fromParts("package", packageName, null)
    }
    startActivity(intent)
}

// 在对话框中提示用户跳转设置
AlertDialog.Builder(this)
    .setTitle("权限被禁用")
    .setMessage("请在设置中启用相机权限。")
    .setPositiveButton("去设置") { _, _ -> openAppSettings() }
    .setNegativeButton("取消", null)
    .show()

四、最佳实践与注意事项

1. 按需请求权限

  • 场景化请求:在用户触发相关功能时请求权限(如点击“拍照”按钮时请求相机权限)。
  • 避免批量请求:一次性请求过多权限会降低用户信任。

2. 适配 Android 版本

  • Android 11 (API 30)+:需在 AndroidManifest.xml 中声明 <queries> 以访问其他应用。
  • Android 13 (API 33)+:新增细粒度权限(如 READ_MEDIA_IMAGES 替代 READ_EXTERNAL_STORAGE)。

3. 使用权限库简化代码

  • Google 的 Activity Result API:官方推荐,与生命周期绑定。
  • PermissionsDispatcher:通过注解简化流程:
    kotlin
    @RuntimePermissions
    class MainActivity : AppCompatActivity() {
        @NeedsPermission(Manifest.permission.CAMERA)
        fun openCamera() { /* ... */ }
    
        override fun onRequestPermissionsResult(...) {
            super.onRequestPermissionsResult(...)
            onRequestPermissionsResult(requestCode, grantResults)
        }
    }

4. 测试权限场景

  • ADB 命令管理权限
    bash
    adb shell pm grant <package> <permission>  # 授予权限
    adb shell pm revoke <package> <permission> # 撤销权限
  • 模拟用户拒绝:测试应用在权限被拒绝后的降级逻辑。

五、完整示例:请求相机权限

kotlin
class CameraActivity : AppCompatActivity() {
    private val requestPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) {
            openCamera()
        } else {
            handlePermissionDenied()
        }
    }

    fun openCamera() {
        // 实际打开相机的逻辑
    }

    fun checkCameraPermission() {
        when {
            ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.CAMERA
            ) == PackageManager.PERMISSION_GRANTED -> {
                openCamera()
            }
            ActivityCompat.shouldShowRequestPermissionRationale(
                this,
                Manifest.permission.CAMERA
            ) -> {
                showRationaleDialog()
            }
            else -> {
                requestPermissionLauncher.launch(Manifest.permission.CAMERA)
            }
        }
    }

    private fun showRationaleDialog() {
        AlertDialog.Builder(this)
            .setTitle("需要相机权限")
            .setMessage("此功能需要访问相机以拍摄照片。")
            .setPositiveButton("允许") { _, _ ->
                requestPermissionLauncher.launch(Manifest.permission.CAMERA)
            }
            .setNegativeButton("取消", null)
            .show()
    }

    private fun handlePermissionDenied() {
        AlertDialog.Builder(this)
            .setTitle("权限被禁用")
            .setMessage("请在设置中启用相机权限。")
            .setPositiveButton("去设置") { _, _ -> openAppSettings() }
            .setNegativeButton("取消", null)
            .show()
    }

    private fun openAppSettings() {
        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
            data = Uri.fromParts("package", packageName, null)
        }
        startActivity(intent)
    }
}

六、常见问题与解决方案

1. 权限已在 Manifest 声明,但依然被拒绝

  • 检查点:确保在 Manifest 中正确声明权限,且设备 API 级别符合要求。

2. 用户多次拒绝后不再提示

  • 解决方案:引导用户到应用设置页手动开启权限。

3. 权限组变更导致意外授权

  • 注意:虽然同一组的权限可能被自动授予,但应显式请求每个需要的权限。

通过合理设计权限请求逻辑,开发者既能保障用户隐私,又能提升应用体验。始终遵循最小权限原则,并在必要时清晰解释权限用途,以建立用户信任。