3. Android权限模型(一):权限分类、声明请求与权限组
各位同学,今天我们正式进入Android安全架构的核心地带——权限模型。说实话,权限这块内容,我当年刚入行时也觉得不就是弹个窗问用户同不同意嘛,有什么好讲的?直到我在一个企业级项目中,因为一个权限声明的位置写错了,导致整个功能在Android 11上直接崩溃……嗯,从那以后,我再也不敢小看这几行配置了。
3.1 权限分类:普通、危险、签名
Android把权限分成了几个等级。你想想看,如果所有权限都弹窗问用户,用户早就烦死了。反过来,如果所有权限都静默授权,那隐私安全就形同虚设。所以Google搞了一套分级机制。
| 保护级别 | 授权方式 | 典型例子 |
|---|---|---|
| normal(普通) | 安装时自动授予 | INTERNET、ACCESS_NETWORK_STATE |
| dangerous(危险) | 运行时弹窗请求 | CAMERA、READ_CONTACTS、ACCESS_FINE_LOCATION |
| signature(签名) | 同签名自动授予 | BIND_ACCESSIBILITY_SERVICE、系统级API |
关键点:普通权限在Android 6.0之后仍然是安装时自动授予,不需要你写运行时请求代码。但危险权限必须动态申请。签名权限嘛,只有和你用同一个签名的应用才能拿到,说白了就是「自己人」用的。
我个人的习惯是,在项目初期就把所有用到的权限列一个表,标清楚每个权限的保护级别。这样后面写代码时就不会漏掉运行时请求的逻辑。我曾经在一个外包项目里吃过亏——对方要求支持Android 4.4,结果我全用了动态请求,老设备上直接闪退。所以,一定要检查minSdkVersion。
3.2 权限声明与请求
权限声明必须在AndroidManifest.xml里写,这是铁律。你代码里调了Camera,但清单文件里没写<uses-permission>,那系统直接抛SecurityException。我见过不少新手犯这个错。
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
对于危险权限,光声明还不够。你需要在代码里检查是否已经授权,如果没有,就发起请求。为什么?因为用户可以在设置里随时关掉权限。你想想看,用户今天给了你定位权限,明天心情不好去设置里关了,你的App如果没做检查,直接调用定位API,那就崩了。
// 检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// 请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CODE_CAMERA);
} else {
// 已经授权,直接打开相机
openCamera();
}
避坑指南:我曾经遇到一个情况——用户第一次拒绝权限时勾选了「不再询问」,之后再次请求时系统直接拒绝弹窗。这时候你应该引导用户去设置里手动开启。判断方法:shouldShowRequestPermissionRationale() 返回false,且当前权限未授予,就说明被永久拒绝了。
3.3 权限组
权限组这个概念,说白了就是Google把相关的危险权限归到了一起。比如定位相关的有ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION,它们都属于LOCATION组。
为什么要搞权限组?因为用户体验。你想想看,如果用户授权了精确位置,那粗略位置还需要再问一次吗?不需要。系统会自动把同组的其他权限一并授予。
| 权限组 | 包含的权限 |
|---|---|
| CALENDAR | READ_CALENDAR, WRITE_CALENDAR |
| CAMERA | CAMERA |
| CONTACTS | READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS |
| LOCATION | ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION |
| PHONE | READ_PHONE_STATE, CALL_PHONE, READ_CALL_LOG, ... |
| STORAGE | READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE |
注意:权限组的行为在不同Android版本上有差异。在Android 8.0之前,只要你申请了组内的一个权限,用户授权后同组其他权限也自动获得。但在Android 8.0之后,Google收紧了策略——你仍然只需要请求一次,但用户可以在设置里单独关闭某个权限。所以,不要假设同组权限一定可用。
我个人建议,在请求权限时,尽量按功能模块来组织。比如你要拍照,就只请求CAMERA权限,不要顺带把STORAGE也一起请求了。用户会觉得很奇怪:「我就拍个照,你为什么要读我的存储?」这种体验很糟糕。
3.4 知识体系总览
下面这张图是我自己整理的权限模型核心脉络,你可以把它当作本章的思维导图。
这张图把权限分类、声明请求、权限组三个核心知识点串在了一起。你仔细看,其实整个权限模型就围绕一个核心原则:最小权限。只申请你需要的权限,只在你需要的时候申请,用户拒绝后要有优雅的降级方案。
总结一下本章重点:
- 普通权限:声明即可,无需运行时请求
- 危险权限:声明 + 运行时检查 + 请求 + 回调处理
- 签名权限:同签名应用自动获取,常用于系统级API
- 权限组:方便用户,但不要依赖同组权限自动授予
- 永远检查
shouldShowRequestPermissionRationale()处理被拒绝的情况
好了,这一章的内容就到这里。权限模型是Android安全的基础,后面讲沙箱和SELinux时还会反复用到这些概念。你先把今天的内容消化掉,尤其是权限请求的代码,最好自己动手写一遍。