4. Android权限体系演进:从Android 6.0运行时权限到Android 13细粒度权限
聊到Android隐私合规,权限体系是绕不开的核心话题。我做了这么多年Android开发,亲眼看着权限管理从「粗放式」一步步走到今天的「精细化」。说实话,早期那会儿的权限设计,放到现在合规环境下,简直是个灾难。
这一节,我就带你捋一遍Android权限体系的演进脉络。你理解了为什么这么变,自然就知道该怎么适配了。
4.1 为什么权限体系必须演进?
先问个问题:你装一个手电筒App,它为什么要读取你的联系人?
嗯,这个问题在Android 6.0之前,没人管。开发者只要在AndroidManifest.xml里声明了权限,用户安装时点一下「同意」,App就能为所欲为。我当年接手过一个项目,里面有个功能只是给图片加个滤镜,结果权限声明里赫然写着READ_CONTACTS。问产品经理,他说「先申着,万一以后要用呢」——你看,这就是典型的野蛮生长。
从GDPR到国内的《个人信息保护法》,法律层面要求App只能收集「最小必要」的数据。所以Google不得不从系统层面把权限管起来。说白了,权限体系的每一次升级,都是在跟「滥用权限」这件事较劲。
4.2 关键里程碑:从6.0到13
我整理了一张演进图,你先有个整体印象:
4.3 Android 6.0:运行时权限——划时代的变革
Android 6.0(API 23)引入了运行时权限模型。这是整个权限体系的分水岭。
核心变化:
- 危险权限(Dangerous Permissions)需要在运行时动态申请
- 用户可以在「设置」中随时撤销已授予的权限
- App必须检查权限状态,处理被拒绝的情况
我记得当时适配6.0的时候,整个团队都懵了。以前写代码哪管什么权限检查,直接调API就行。现在倒好,每个敏感操作前面都得加一段权限判断逻辑。
关键概念:权限分组
Google把危险权限分成了若干组(Permission Group)。同一组内的权限,只要申请其中一个并得到授权,整组权限就自动获得了。举个例子:你申请了CAMERA,系统会自动把同组的RECORD_AUDIO也带上——虽然你根本没要。
这一点在合规审计时经常被忽略。我见过不少App,明明只用了拍照功能,却因为权限分组机制,悄悄拿到了录音权限。审计报告里直接标红。
4.4 权限分组与敏感权限列表
说到权限分组,我直接给你看官方定义的几大危险权限组:
| 权限组 | 包含的权限 | 典型场景 |
|---|---|---|
| CALENDAR | READ_CALENDAR, WRITE_CALENDAR | 日程管理App |
| CAMERA | CAMERA | 拍照、扫码 |
| CONTACTS | READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS | 通讯录同步 |
| LOCATION | ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, ACCESS_BACKGROUND_LOCATION | 地图、导航 |
| PHONE | READ_PHONE_STATE, CALL_PHONE, READ_CALL_LOG, WRITE_CALL_LOG, ADD_VOICEMAIL, USE_SIP, PROCESS_OUTGOING_CALLS | 通话相关 |
| STORAGE | READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE | 文件读写 |
⚠️ 避坑指南:我曾经在一个社交App项目里,因为用了READ_PHONE_STATE来获取设备标识,结果被第三方检测工具判定为「过度收集隐私」。后来才发现,这个权限属于PHONE组,一旦申请,整组权限都会被用户看到——哪怕你只用了IMEI。合规做法是改用AdvertisingId或者InstanceId,彻底绕开电话权限。
4.5 Android 10-11:分区存储与单次权限
Android 10(API 29)引入了分区存储(Scoped Storage)。说白了,就是不让App随便翻用户的相册和文件了。每个App只能在自己的沙盒目录里自由读写,访问公共目录必须走MediaStore。
到了Android 11(API 30),又加了单次权限(One-time Permission)。用户可以选择「仅本次允许」,App退出后权限自动回收。这个设计太聪明了——你想想看,用户扫描一个二维码,授权一次就够了,凭什么让App一直拿着相机权限?
💡 我的建议:适配Android 11时,一定要处理好权限被自动重置的情况。我见过一个App,用户授权了位置权限,用了一次后权限被重置,下次打开直接崩溃——因为没做权限状态检查。正确的做法是:每次使用敏感权限前,都调用checkSelfPermission()重新确认。
4.6 Android 13:细粒度权限——精准到每一张照片
Android 13(API 33)把权限细化到了极致。以前你申请READ_EXTERNAL_STORAGE,就能读到所有媒体文件。现在不行了,Google把媒体权限拆成了三个:
READ_MEDIA_IMAGES—— 只读图片READ_MEDIA_VIDEO—— 只读视频READ_MEDIA_AUDIO—— 只读音频
另外,BLUETOOTH相关的权限也变了。以前要申请ACCESS_FINE_LOCATION才能做蓝牙扫描,现在Android 13引入了BLUETOOTH_SCAN、BLUETOOTH_ADVERTISE、BLUETOOTH_CONNECT三个独立权限,不再跟位置权限强绑定。
还有一个容易被忽略的点:通知权限。Android 13把通知也纳入了运行时权限体系。App必须申请POST_NOTIFICATIONS才能推送通知。我去年帮一个客户做合规改造,他们的App一升级到Android 13,通知全没了——就是因为没适配这个新权限。
4.7 权限申请最佳实践
讲了这么多历史,最后落到实操上。我总结了一套权限申请的「黄金流程」,你在项目里可以直接套用:
- 提前说明用途:不要一上来就弹系统对话框。先弹一个自定义的说明弹窗,告诉用户「我们需要访问相册来选择头像」,用户理解了,同意的概率高很多。
- 最小化申请:只申请当前功能必需的权限。别学我当年那个产品经理,把权限当储备粮。
- 处理拒绝:用户拒绝后,不要反复弹窗。如果用户勾选了「不再询问」,引导他去设置里手动开启。
- 检查权限状态:每次使用前都检查,不要缓存授权结果。用户随时可能去设置里关掉权限。
- 适配新版本:targetSdkVersion 及时更新到33+,用新的细粒度权限替代旧的。
代码示例(Kotlin):
// 检查并申请单张图片访问权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Android 13+ 使用细粒度权限
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_MEDIA_IMAGES)
!= PackageManager.PERMISSION_GRANTED) {
// 先解释为什么需要这个权限
showPermissionRationale("选择头像需要访问您的相册")
// 再发起申请
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.READ_MEDIA_IMAGES),
REQUEST_CODE_PICK_IMAGE)
}
} else {
// 低版本使用传统存储权限
// ... 兼容处理
}
核心原则:权限申请不是技术问题,是用户体验问题。你让用户感到被尊重,他就愿意授权。你硬来,他就去投诉你——到时候GDPR的罚单可不是闹着玩的。
好了,这一节的内容就到这里。权限体系这块水很深,但只要你理解了演进背后的逻辑——从「能用就行」到「最小必要」——适配起来就不会跑偏。