一、运行时权限的背景与分类
1. 为什么需要运行时权限?
- 用户隐私保护:防止应用在用户不知情时滥用权限(如获取通讯录、定位等)。
- 最小权限原则:按需申请权限,减少不必要的权限请求。
- 透明化操作:用户可随时在系统设置中调整权限状态。
2. 权限分类
- 普通权限(Normal):不涉及隐私,系统自动授予(如网络访问
INTERNET
)。 - 危险权限(Dangerous):涉及隐私,需运行时申请(如
CAMERA
、READ_CONTACTS
)。 - 特殊权限(Special):需用户手动在系统设置中开启(如悬浮窗
SYSTEM_ALERT_WINDOW
)。
常见危险权限组:
权限组 | 包含权限示例 |
---|---|
CALENDAR | READ_CALENDAR , WRITE_CALENDAR |
CAMERA | CAMERA |
CONTACTS | READ_CONTACTS , WRITE_CONTACTS |
LOCATION | ACCESS_FINE_LOCATION , ACCESS_COARSE_LOCATION |
MICROPHONE | RECORD_AUDIO |
STORAGE | READ_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. 权限组变更导致意外授权
- 注意:虽然同一组的权限可能被自动授予,但应显式请求每个需要的权限。
通过合理设计权限请求逻辑,开发者既能保障用户隐私,又能提升应用体验。始终遵循最小权限原则,并在必要时清晰解释权限用途,以建立用户信任。