Android 权限系统详解——跨平台开发者的权限管理指南
Android 权限分普通/危险/特殊三级,危险权限必须运行时申请,Android 13+ 又拆出通知与细粒度媒体权限; 跨平台应用"权限被拒崩溃""收不到推送""后台定位拿不到",根因都在权限时序与版本差异。
一句话概括
Android 权限是应用访问敏感能力(相机、定位、通讯录、存储)的”门禁”:系统按风险把权限分成普通(装好即给)、危险(要弹窗问用户)、特殊(要跳设置页手动开)三级。
对跨平台开发者,权限是最容易”看起来配了却用不了”的地带——危险权限光在 AndroidManifest 声明不够,还得运行时弹窗;Android 6 引入运行时模型、Android 13 又把通知和媒体权限拆细。配错 = 崩溃或功能静默失效,配对 = 用户顺滑授权。面试里它几乎必考,因为坑又多又真实。
一句话结论:声明只是”申请资格”,运行时授权才是”拿到钥匙”;而 Android 13+ 之后,钥匙还被拆成一把把小的。
核心知识点
1. 三级权限模型
1
2
3
4
5
6
7
8
9
10
<!-- 普通权限:声明即授予,用户无感知 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 危险权限:还要运行时弹窗 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 特殊权限:无法用标准弹窗,必须跳系统设置页 -->
<!-- SYSTEM_ALERT_WINDOW / WRITE_SETTINGS / MANAGE_EXTERNAL_STORAGE -->
| 级别 | 代表权限 | 授予方式 |
|---|---|---|
| 普通(Normal) | INTERNET、VIBRATE、ACCESS_NETWORK_STATE、WAKE_LOCK | 安装/声明即自动给 |
| 危险(Dangerous) | CAMERA、READ_CONTACTS、ACCESS_FINE_LOCATION、RECORD_AUDIO | 运行时弹窗问用户 |
| 特殊(Special) | SYSTEM_ALERT_WINDOW、WRITE_SETTINGS、MANAGE_EXTERNAL_STORAGE | 跳设置页手动开 |
危险权限按”权限组”归类(如 CAMERA、LOCATION、MICROPHONE),组主要影响系统设置里怎么展示给用户,以及安装期(Android 6 之前)的分组授权。
2. 运行时申请:现代写法(Activity Result API)
onRequestPermissionsResult 已废弃,现代用 registerForActivityResult:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class MainActivity : AppCompatActivity() {
// 单个权限
private val reqCamera = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) openCamera() else handleDenied()
}
// 多个权限一起申请
private val reqMedia = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { result ->
val images = result[READ_MEDIA_IMAGES] == true
val video = result[READ_MEDIA_VIDEO] == true
// ...
}
fun askCamera() {
when {
// ✅ 已授权:直接用
ContextCompat.checkSelfPermission(this, CAMERA) == PERMISSION_GRANTED ->
openCamera()
// ✅ 之前拒绝过且还能再问:先解释为什么需要
shouldShowRequestPermissionRationale(CAMERA) ->
showRationale { reqCamera.launch(CAMERA) }
// ✅ 首次 / 用户未"永久拒绝":直接弹窗
else -> reqCamera.launch(CAMERA)
}
}
}
shouldShowRequestPermissionRationale 的三种含义:
| 场景 | 返回值 | 该做什么 |
|---|---|---|
| 从未请求过 | false | 直接弹窗 |
| 拒绝过但没”不再询问” | true | 先展示说明,再弹窗 |
| 拒绝过且”不再询问”(API 30+ 连拒两次) | false | 引导去系统设置页 |
3. 权限组自动授权:Android 11 已改
早年(Android 6~10)有个”便利”:同组里只要有一个权限已授予,再请求同组另一个会静默自动给,不弹窗。这点 Android 11(API 30)起已废弃——现在请求任意危险权限都会正常弹窗,组内不再自动连带授权。
1
2
Android 6~10:组内已授权 → 新权限静默通过
Android 11+ :每次请求都弹窗,组内不再"搭便车"
另外两个 API 30 的关键变化:
- 一次性授权:位置/相机/麦克风弹窗出现”仅此一次”,离开前台即失效。
- 自动重置:应用几个月没被打开,系统自动收回其运行时权限,下次启动要重新申请——所以永远假设权限可能被收回,用前先 check。
4. Android 13(API 33):通知 + 细粒度媒体
这是近年改动最大的一版,跨平台推送”突然收不到”多半因为它。
1
2
3
4
5
6
7
8
9
10
<!-- 通知权限:必须运行时申请,否则收不到推送 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- 媒体被拆成三个,替代老的 READ_EXTERNAL_STORAGE -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<!-- 附近 Wi-Fi 设备:不再需要位置权限 -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
向后兼容写法(老版本用存储权限,Android 13+ 用媒体权限):
1
2
3
4
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
5. 后台定位:危险权限里的”特殊户”
ACCESS_BACKGROUND_LOCATION 虽属危险权限,但获取流程更苛刻:
1
2
3
4
5
6
7
8
// 1) 必须先拿到前台位置(精/粗)
reqFine.launch(ACCESS_FINE_LOCATION)
// 2) 再单独申请后台位置;Android 10+ 不能靠标准弹窗拿到,需引导去设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
Uri.parse("package:$packageName"))
// 实际常跳 Settings > 应用 > 权限 > 位置 > 始终允许
}
⚠️ Google Play 对使用后台定位的应用严审,必须证明核心功能依赖它,否则上架被拒。
6. 声明技巧:maxSdkVersion 与 uses-permission-sdk-23
1
2
3
4
5
6
7
8
<!-- 只在 Android 12 及以下声明老存储权限 -->
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- 只在 API 23+ 设备声明(低版本根本没这权限概念) -->
<uses-permission-sdk-23
android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
maxSdkVersion让某权限只在指定版本以下出现,避免在高版本”挂着不用”惹审计怀疑。uses-permission-sdk-23让权限仅对运行时模型设备(API 23+)生效。
其实你每天都在用
- 拍照(image_picker):要
CAMERA;Android 12- 还需READ_EXTERNAL_STORAGE,Android 13+ 换成READ_MEDIA_IMAGES。 - 定位导航:前台用
ACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATION,后台还得单独申请ACCESS_BACKGROUND_LOCATION并过商店审核。 - 收推送(Android 13+):不申请
POST_NOTIFICATIONS就完全收不到通知,且用户可在设置里单独关掉。 - 选相册/文件:Android 13+ 按图片/视频/音频三类细粒度授权,不再是一个”存储”大权限。
- 录音/语音:
RECORD_AUDIO危险权限,运行时申请;麦克风灯会亮。 - 悬浮窗(悬浮球/悬浮播放):
SYSTEM_ALERT_WINDOW是特殊权限,必须跳设置页手动开。 - 读通讯录/日历:
READ_CONTACTS/READ_CALENDAR危险权限,用户随时可收回。 - 权限被拒时:别死循环弹窗,做”优雅降级”(如提示手动开启或换替代方案)。
常见误解(FAQ)
❌ 误区一:”清单里写了 uses-permission 就能直接用”
只对普通权限成立。INTERNET 这类声明即给;但 CAMERA、READ_CONTACTS 等危险权限只是”拿到申请资格”,真正能用还要在运行时弹窗、用户点同意(Android 6+)。漏了运行时这一步,调用即 SecurityException / Permission Denial 崩溃。
❌ 误区二:”用户同意了相机,同组麦克风也自动有了”
这是 Android 11 之前的旧行为,现已废弃。API 30+ 每个危险权限都要单独弹窗、单独授权,组内不再”搭便车”。所以申请多个权限时,该用 RequestMultiplePermissions 一个个问清楚,别指望授权一个带一串。
❌ 误区三:”用户拒绝一次,以后就再也弹不出来了”
不是一次,是连续拒绝两次(API 30+)才被视为”不再询问”,此后系统不再弹窗、直接走”引导去设置”。首次拒绝、或只拒绝过一次,shouldShowRequestPermissionRationale 仍会返回 true,你还能解释后再次请求。
❌ 误区四:”通知权限一直都有,推送照常”
Android 13(API 33)起,POST_NOTIFICATIONS 变成需要运行时申请的危险权限。没申请或超过半数用户拒绝,你的推送就石沉大海——这也是很多 App 升级后”推送到达率腰斩”的真凶。而且它能被用户在设置里单独关,要处理”未授权”分支。
❌ 误区五:”后台定位和前台定位一起申请就行”
不行。ACCESS_BACKGROUND_LOCATION 必须在前台位置已授予之后单独申请,且 Android 10+ 无法靠标准弹窗拿到——通常要引导用户去系统设置把位置设为”始终允许”。加上 Play 对后台定位严审,滥用会被拒。普通场景用前台定位就够了。
一句话总结
Android 权限是一条”声明 → 运行时授权 → 按版本细分”的链路:普通权限装好即给,危险权限要弹窗、且 Android 11+ 逐个体授权、Android 13+ 又把通知与媒体拆细,特殊权限还得跳设置页;记住”用前先 check、被拒要降级、版本要分流”,跨平台权限问题就从玄学变成可预期的标准流程。