Android 权限体系入门:你到底该了解什么?
提到 Android 权限,很多开发者第一反应就是「请求一下就行了」。但说实话,我见过太多 SDK 因为权限问题被下架,甚至牵连宿主应用。做第三方 SDK,权限合规是你绕不开的坎。今天咱们就从最基础的地方开始——权限级别、运行时机制和权限组。把这些搞明白了,后面分析案例才不会晕头转向。
1. 权限级别:Normal、Dangerous 和 Signature
Android 把权限分了几个等级,官方的叫法是 protection level。说白了就是:这个权限到底有多危险?我习惯把它们分成三档:
| 级别 | 系统行为 | 是否需要用户同意 | 常见例子 |
|---|---|---|---|
| Normal | 安装时自动授予,不可撤销 | 否 | INTERNET、ACCESS_NETWORK_STATE |
| Dangerous | 运行时动态请求,用户可拒绝 | 是(弹窗) | CAMERA、ACCESS_FINE_LOCATION、READ_CONTACTS |
| Signature | 仅当请求方和系统签名相同时才授予 | 否(不能手动干涉) | BIND_NOTIFICATION_LISTENER_SERVICE |
这里有个坑要提醒你:Normal 权限虽然自动给,但谷歌 Play 依然会审查你的使用场景。 我之前有个项目在 Manifest 里声明了 INTERNET 权限用于埋点上报,结果被警告「不合理使用网络权限」。虽然最后申诉通过了,但也浪费了一周时间。嗯,所以哪怕是 Normal 权限,也得说清楚为什么需要。
至于 Signature 权限,普通第三方 SDK 几乎用不到,但如果你做系统级开发或者对接厂商私有接口,就得小心了。我记得有一次调试一个支付 SDK,对方说需要某个 Signature 权限才能调起系统安全键盘。结果发现签名不对,调了半天才找到原因——签名证书不一致。
2. 运行时权限机制:从 Android 6.0 说起
在 Android 6.0(API 23)之前,App 安装时把所有权限一口气全要了,用户要么全给,要么不安装。这其实很流氓。6.0 之后改成了 运行时权限模型——用户可以在使用过程中随时拒绝或者关闭某个权限。
具体流程大概是这样的:
- 你的代码调用
requestPermissions()请求某个 Dangerous 权限。 - 系统弹出标准对话框,用户选择「允许」或「拒绝」。
- 在
onRequestPermissionsResult()回调中拿到结果。 - 如果用户选了「不再询问」,后续再请求不会弹窗,直接拒绝。
你是不是觉得很简单?但实际开发中翻车率很高。我给你看一段典型代码:
// 请求摄像头权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);
} else {
// 已有权限,直接打开相机
openCamera();
}
这段代码有个隐藏问题:没有判断 shouldShowRequestPermissionRationale()。用户第一次拒绝后,如果你再次傻乎乎地直接请求,系统不给弹窗,你就永远拿不到权限。
shouldShowRequestPermissionRationale(),如果返回 false 就引导用户去设置页手动开启。
技术上还有一个点容易被忽略:权限请求的回调必须在 Activity 的 onRequestPermissionsResult 中处理。如果你在 Fragment 里请求,记得用 Fragment.requestPermissions(),对应的回调会走 Fragment 自己的。很多第三方 SDK 为了解耦,直接在 Service 或普通 Java 类里请求权限——那是行不通的。
3. 权限组:系统帮你“偷懒”的设计
你有没有想过,为什么有些权限申请一次就够了?比如你请求了 READ_CONTACTS,系统可能连 WRITE_CONTACTS 都一起给了。这背后就是 权限组(Permission Group) 的概念。
Android 将逻辑相关的 Dangerous 权限归为一组。下表是几个常见的:
| 权限组 | 组内权限 |
|---|---|
| STORAGE | READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE |
| CAMERA | CAMERA |
| CONTACTS | READ_CONTACTS、WRITE_CONTACTS、GET_ACCOUNTS |
| LOCATION | ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION |
规则很简单:同一权限组内,只要用户授权了其中一个,该组内的其他权限也会被自动授予,不再弹窗。 这是为了方便用户,避免反复弹窗惹人烦。但这对 SDK 开发者来说是个双刃剑——
我遇到过一种情况:SDK 只声明了 READ_CONTACTS,但用户之前给另一个应用授权过 WRITE_CONTACTS,于是系统在我们打包的 APK 里自动把 READ_CONTACTS 也通过了。结果我们的 SDK 在没有声明 WRITE_CONTACTS 的情况下,居然能写通讯录?其实不行,因为权限授予的是「该组在 Manifest 中声明的权限子集」。如果没声明,即使组内其他权限被授予了,你也没有。这个地方容易引起混淆。
整体知识框架
下面这张图把这些概念串起来了:
总结一下
- Normal 权限:安全、自动,但也要有业务场景支撑。
- Dangerous 权限:最容易被用户拒绝,必须做好
shouldShowRequestPermissionRationale处理。 - Signature 权限:签名一致才能拿,非必要不用。
- 运行时权限:记住只在 Activity/Fragment 里请求,并处理回调。
- 权限组:能帮你少弹窗,但要注意你的 Manifest 声明了哪些。
把这些基础打牢,后面我跟你分析具体 SDK 的权限违规案例时,你就能一眼看出问题出在哪个环节了。