一、动态权限概述
自 Android 6.0(API 23)起,系统引入 运行时权限模型,危险权限需在运行时动态申请。本文将从封装、使用到兼容性全面解析。
二、动态权限使用场景
1. 危险权限申请
- 摄像头、位置、存储、通讯录等敏感权限。
2 权限组处理
- 请求同一权限组的多个权限时,系统可能合并弹窗(如
STORAGE
组的读写权限)。
3. 自定义权限提示
- 用户拒绝后,展示解释对话框并引导至设置页。
三、动态权限封装实践
1. 基础权限工具类封装
kotlin
object PermissionUtils {
// 检查权限是否已授予
fun checkPermission(context: Context, permission: String): Boolean {
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
}
// 批量检查权限
fun checkPermissions(context: Context, permissions: Array<String>): BooleanArray {
return permissions.map { checkPermission(context, it) }.toBooleanArray()
}
// 请求权限(带回调)
fun requestPermissions(
activity: FragmentActivity,
permissions: Array<String>,
requestCode: Int,
callback: (granted: BooleanArray) -> Unit
) {
val launcher = activity.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { grants ->
callback(grants.values.map { it }.toBooleanArray())
}
launcher.launch(permissions)
}
}
2. 链式调用封装(扩展函数)
kotlin
class PermissionRequest(private val activity: FragmentActivity) {
private var permissions: Array<String> = emptyArray()
private var onGranted: (() -> Unit)? = null
private var onDenied: ((deniedPermissions: List<String>) -> Unit)? = null
fun permissions(vararg permissions: String): PermissionRequest {
this.permissions = permissions.toTypedArray()
return this
}
fun onGranted(block: () -> Unit): PermissionRequest {
this.onGranted = block
return this
}
fun onDenied(block: (deniedPermissions: List<String>) -> Unit): PermissionRequest {
this.onDenied = block
return this
}
fun execute() {
if (permissions.isEmpty()) return
val allGranted = permissions.all { PermissionUtils.checkPermission(activity, it) }
if (allGranted) {
onGranted?.invoke()
return
}
PermissionUtils.requestPermissions(activity, permissions, System.currentTimeMillis().toInt()) { grants ->
val deniedList = permissions.filterIndexed { index, _ -> !grants[index] }
if (deniedList.isEmpty()) {
onGranted?.invoke()
} else {
onDenied?.invoke(deniedList)
}
}
}
}
// 使用示例
PermissionRequest(activity)
.permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS)
.onGranted { openCamera() }
.onDenied { showDeniedDialog(it) }
.execute()
四、注意事项
1. 权限声明与分组
- 清单文件声明 :所有危险权限需在
AndroidManifest.xml
中声明。 - 权限组影响:若用户已授权同一组的某个权限,其他权限可能自动授予(Android 10+ 调整此策略)。
2. 用户拒绝处理
-
永久拒绝判断:
kotlinfun shouldShowRationale(activity: FragmentActivity, permission: String): Boolean { return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission) }
-
引导至设置页:
kotlinfun openAppSettings(activity: Activity) { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { data = Uri.fromParts("package", activity.packageName, null) } activity.startActivity(intent) }
3. 特殊权限处理
-
后台定位权限(Android 10+) :需在清单中添加
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
。 -
悬浮窗权限(SYSTEM_ALERT_WINDOW):需特殊处理:
kotlinif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) { val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION) intent.data = Uri.parse("package:${context.packageName}") startActivity(intent) }
五、版本兼容性
1. Android 6.0 以下(API < 23)
- 无需动态申请,安装时自动授予清单中声明的权限。
2. Android 11+(API 30)
- 单次授权 :
ACCESS_BACKGROUND_LOCATION
需单独申请。 - 权限可见性 :未在清单中声明的权限无法调用
checkSelfPermission
。
3. Android 13+(API 33)
- 细化媒体权限 :
READ_MEDIA_IMAGES
、READ_MEDIA_VIDEO
替代READ_EXTERNAL_STORAGE
。 - 附近 Wi-Fi 设备权限 :
NEARBY_WIFI_DEVICES
替代ACCESS_FINE_LOCATION
用于 Wi-Fi 扫描。
六、最佳实践
- 按需申请:仅在需要时请求权限,避免一次性申请所有权限。
- 最小权限原则 :优先使用
READ_EXTERNAL_STORAGE
替代WRITE_EXTERNAL_STORAGE
。 - 测试用例覆盖 :
- 用户首次拒绝。
- 用户勾选"不再询问"。
- 权限被系统自动撤销(如通过 ADB 命令)。
- 使用 Jetpack 安全组件 :如
Security Identity Credential API
替代直接存储敏感数据。
七、完整示例代码
kotlin
// MainActivity.kt
class MainActivity : AppCompatActivity() {
private val PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_CONTACTS
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_open_camera.setOnClickListener {
requestCameraPermission()
}
}
private fun requestCameraPermission() {
PermissionRequest(this)
.permissions(*PERMISSIONS)
.onGranted { openCamera() }
.onDenied { deniedPermissions ->
if (deniedPermissions.any { !shouldShowRationale(it) }) {
showGoToSettingsDialog()
} else {
showRationaleDialog()
}
}
.execute()
}
private fun showRationaleDialog() {
AlertDialog.Builder(this)
.setTitle("权限需要")
.setMessage("请授予相机和通讯录权限以正常使用功能")
.setPositiveButton("确定") { _, _ -> requestCameraPermission() }
.setNegativeButton("取消", null)
.show()
}
private fun showGoToSettingsDialog() {
AlertDialog.Builder(this)
.setTitle("权限被永久拒绝")
.setMessage("请前往设置中手动开启权限")
.setPositiveButton("去设置") { _, _ -> openAppSettings() }
.setNegativeButton("取消", null)
.show()
}
private fun openCamera() {
// 打开相机逻辑
}
private fun shouldShowRationale(permission: String): Boolean {
return ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
}
private fun openAppSettings() {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
startActivity(intent)
}
}
八、总结
- 封装核心:简化权限检查与请求流程,统一处理回调。
- 兼容性关键:适配 Android 6.0+ 动态模型,处理 Android 11+ 细化权限。
- 用户体验:友好引导用户授予权限,避免粗暴拒绝导致流失。