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_LOCATIONACCESS_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 知识体系总览

下面这张图是我自己整理的权限模型核心脉络,你可以把它当作本章的思维导图。

Android权限模型 权限分类 普通(normal) → 安装时自动授予 危险(dangerous) → 运行时弹窗 签名(signature) → 同签名自动 声明与请求 AndroidManifest.xml 声明 checkSelfPermission 检查 requestPermissions 请求 权限组 同组权限一次授权 用户可单独关闭 Android 8.0+ 行为变化 核心原则:最小权限 + 按需请求 + 兼容处理 不要假设权限永远可用,永远做好降级处理

这张图把权限分类、声明请求、权限组三个核心知识点串在了一起。你仔细看,其实整个权限模型就围绕一个核心原则:最小权限。只申请你需要的权限,只在你需要的时候申请,用户拒绝后要有优雅的降级方案。

总结一下本章重点:

  • 普通权限:声明即可,无需运行时请求
  • 危险权限:声明 + 运行时检查 + 请求 + 回调处理
  • 签名权限:同签名应用自动获取,常用于系统级API
  • 权限组:方便用户,但不要依赖同组权限自动授予
  • 永远检查 shouldShowRequestPermissionRationale() 处理被拒绝的情况

好了,这一章的内容就到这里。权限模型是Android安全的基础,后面讲沙箱和SELinux时还会反复用到这些概念。你先把今天的内容消化掉,尤其是权限请求的代码,最好自己动手写一遍。


公众号:蓝海资料掘金营,微信deep3321