[Android 从零到一] 权限管理:运行时权限与最佳实践
从 Android 6.0 (API 23) 开始,Google 引入了运行时权限模型,彻底改变了权限管理的游戏规则。不再是在安装时"一键同意",而是需要开发者在运行时动态请求。尤其是在 Android 11+ 之后,权限机制愈发严格——MANAGE_EXTERNAL_STORAGE、QUERY_ALL_PACKAGES 等新权限的加入,让权限管理成为每个 Android 开发者绕不开的话题。
一、核心概念
1.1 权限分类
Android 将权限分为三类:
- 普通权限(Normal):安装时默认授予,如
INTERNET、ACCESS_NETWORK_STATE。只需在 Manifest 中声明即可。 - 危险权限(Dangerous):涉及隐私数据,如相机、位置、联系人、存储。必须在运行时动态请求。
- 特殊权限(Special):如
SYSTEM_ALERT_WINDOW(悬浮窗)、WRITE_SETTINGS,需要引导用户到设置页面手动开启。
1.2 权限组(Permission Groups)
危险权限按功能分组,同一组内的权限一旦授权一个,其余的也自动获得授权。常用的权限组包括:
CALENDAR—— 日历读写CAMERA—— 相机CONTACTS—— 通讯录LOCATION—— 位置信息STORAGE—— 存储读写(Android 10 后逐渐废弃,改用 Scoped Storage)NOTIFICATION—— 通知权限(Android 13+ 变为运行时权限)READ_MEDIA_*—— 媒体文件访问(Android 13+ 替代READ_EXTERNAL_STORAGE)
二、Kotlin 代码实战
2.1 声明权限
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
2.2 单个权限请求
使用 registerForActivityResult 是官方推荐的现代 API,替代了已废弃的 onRequestPermissionsResult:
class MainActivity : AppCompatActivity() {
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
openCamera()
} else {
showPermissionDeniedDialog()
}
}
private fun checkCameraPermission() {
when {
ContextCompat.checkSelfPermission(
this, Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
openCamera()
}
shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
// 用户之前拒绝过但又没勾"不再询问"
showRationaleDialog {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
else -> {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
}
2.3 多权限请求
private val multiPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions: Map<String, Boolean> ->
val allGranted = permissions.values.all { it }
if (allGranted) {
startFeature()
} else {
val denied = permissions.filter { !it.value }.keys
Snackbar.make(binding.root, "以下权限被拒绝: $denied", Snackbar.LENGTH_LONG)
.setAction("去设置") {
openAppSettings()
}
.show()
}
}
private fun requestPermissions() {
multiPermissionLauncher.launch(
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_FINE_LOCATION
)
)
}
2.4 封装权限工具类
推荐封装一个简洁的权限工具,适配 Fragment / Activity:
object PermissionHelper {
fun hasPermissions(context: Context, vararg permissions: String): Boolean {
return permissions.all {
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
}
fun hasDeniedAndNotAskAgain(
activity: Activity, permission: String
): Boolean {
return ContextCompat.checkSelfPermission(activity, permission)
== PackageManager.PERMISSION_DENIED &&
!activity.shouldShowRequestPermissionRationale(permission)
}
}
三、避坑指南
坑1:忘记处理"不再询问"
当用户勾选"不再询问"并拒绝后,shouldShowRequestPermissionRationale 返回 false。此时应引导用户到应用设置页,而不是再次弹出系统弹窗(会直接返回拒绝)。
private fun openAppSettings() {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.parse("package:$packageName")
}
startActivity(intent)
}
坑2:通知权限(Android 13+)
Android 13 将 POST_NOTIFICATIONS 列为运行时权限。很多 App 在 13 设备上发不出通知就是这个原因。记得在合适的时机请求,首次冷启动直接弹权限容易被打回。
坑3:存储权限在 Android 10+ 的变化
Android 10 引入 Scoped Storage,READ_EXTERNAL_STORAGE 基本失效。访问媒体用 MediaStore API,访问文件用 SAF(Storage Access Framework)。Android 13 又用 READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO 进一步细分。
坑4:registerForActivityResult 必须在生命周期早期注册
这个 API 必须在 onCreate(bundle) 之前(或其中)注册,不能在按钮点击后才创建,否则会抛出 IllegalStateException。
四、总结
Android 权限管理看似简单,实则随着版本迭代越来越复杂。核心要点:
- 用
registerForActivityResult替代老旧的onRequestPermissionsResult - 危险权限必须运行时请求,普通权限只需 Manifest 声明
- 处理好"不再询问"场景,引导用户跳转设置页
- 关注 Android 版本差异(存储权限、通知权限)做兼容
- 请求权限时给足上下文说明,别让用户一脸懵逼地点拒绝
权限设计本质是隐私保护与功能之间的平衡——宁可少要一个权限,也别让用户卸掉你的 App。

浙公网安备 33010602011771号