4、Android权限模型演变:从6.0到14.0的关键变化归纳

做 targetSdk 升级这么多年,权限模型是最让我头疼的模块之一。从 Android 6.0 到 14.0,权限设计经历了三次大的重构。你想想看,以前一个清单文件搞定的事,现在要写几十行运行时申请代码。

我总结了六个关键转折点。每个点都在实际项目里踩过坑。下面咱们一个一个捋。

4.1 Android 6.0 (API 23):运行时权限的革命

这是权限模型的分水岭。6.0 之前,用户安装时授权,之后就不能再改。6.0 之后,权限改成了运行时动态申请。

说白了,就是把"一次性授权"变成了"按需授权"。用户可以在设置里随时关掉某个权限。应用不能再假设"安装时给了就一直有"。

我记得第一次升级 targetSdk 到 23 时,代码里到处是运行时权限的判断。一个简单的拍照功能,得先检查权限,再申请,再处理回调。代码量直接翻倍。

核心变化:
  • 危险权限需要在运行时通过 requestPermissions() 申请
  • 用户可随时在设置中撤销权限
  • 应用必须在每次使用权限前检查是否已被授予
// 6.0 之后的标准权限申请写法
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.CAMERA},
            REQUEST_CODE_CAMERA);
} else {
    // 已经有权限,直接打开相机
    openCamera();
}
注意:如果你的 targetSdk 低于 23,系统会默认采用"安装时授权"的兼容模式。但这只是暂时的,Google 要求所有新应用必须适配运行时权限。

4.2 Android 8.0–9.0:权限分类与组管理

8.0 引入了权限组的概念。同一个组的权限,只要用户授权了一个,同组的其他权限就自动获得授权。

举个例子,CAMERA 和 RECORD_AUDIO 属于不同组。但 READ_CONTACTS、WRITE_CONTACTS、GET_ACCOUNTS 属于同一个"联系人"组。

实际操作中,这其实没太大影响。我个人习惯还是每个权限单独申请,避免用户产生困惑。你想想看,用户明明只点了同意拍照,结果通讯录权限也被偷偷拿了,体验很不好。

9.0 主要调整了权限组的归属关系。比如把 ACTIVITY_RECOGNITION 独立出来。这些细节在升级回测时很容易被忽略。

4.3 Android 10.0–11.0:精细化与自动化

这两个版本是权限模型走向精细化的关键节点。我从 10.0 开始真正感受到了权限管理的复杂度。

10.0 关键拆分:
  • 后台位置权限从普通位置权限中拆分出来,需单独申请 ACCESS_BACKGROUND_LOCATION
  • 存储权限开始实施分区存储(Scoped Storage),不再直接访问共享目录
  • 新增 ACCESS_MEDIA_LOCATION 权限用于读取图片地理位置

我在项目中遇到过存储权限拆分的问题。升级 targetSdk 到 29 后,原有的文件访问代码全炸了。图片保存路径变了,文件扫描方式也得改。当时排查了很久才发现是 Scoped Storage 的限制。

// 10.0 申请后台位置权限示例
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    ActivityCompat.requestPermissions(this,
            new String[]{
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_BACKGROUND_LOCATION
            },
            REQUEST_CODE_LOCATION);
}

11.0 带来了两个重要机制:

  • 一次性权限:用户可以选择"仅本次允许",应用下次使用时需要重新申请。这个设计我很喜欢——类似 iOS 的"临时授权"。
  • 自动重置权限:如果应用超过两个月没被使用,系统会自动撤销所有已授予的运行时权限。这意味着应用在后台被唤醒时,可能会因为权限丢失而崩溃。
避坑提醒:我曾经在升级 targetSdk 到 30 后,遇到用户反馈应用闪退。排查发现是自动重置权限导致的。应用在后台被系统唤醒,权限已经没了,但代码没有做重新检查。

4.4 Android 13.0–14.0:细粒度与后台限制

这两个版本把权限模型推向了极致细粒度。每次升级都像在拆盲盒。

13.0 的三大变化:

  • 细粒度媒体权限:READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO 替代了原来的 READ_EXTERNAL_STORAGE。你得明确告诉用户你要读取哪种类型的文件。
  • 通知权限变成运行时权限:POST_NOTIFICATIONS 需要动态申请。如果用户拒绝,应用无法弹出任何通知。
  • 附近的设备权限:NEARBY_WIFI_DEVICES 替代了原来的 ACCESS_FINE_LOCATION 用于 Wi-Fi 扫描。
// 13.0 申请细粒度媒体权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    ActivityCompat.requestPermissions(this,
            new String[]{
                    Manifest.permission.READ_MEDIA_IMAGES,
                    Manifest.permission.READ_MEDIA_VIDEO
            },
            REQUEST_CODE_MEDIA);
} else {
    // 低版本使用旧的存储权限
    ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
            REQUEST_CODE_STORAGE);
}

我因为通知权限踩过一个坑。升级 targetSdk 到 33 后,应用首次打开没有任何通知。调试了半天才发现是忘了申请 POST_NOTIFICATIONS。通知权限之前一直是静默授予的,突然变成运行时权限,很多人都没意识到。

14.0 的核心收紧:

  • 后台位置权限进一步限制。即使应用获得了 ACCESS_BACKGROUND_LOCATION,系统也会定期提醒用户检查。
  • 新增"选择性照片访问"模式。用户可以选择只给某几张照片的权限,而不是整个图库。
  • 后台行为权限更加严格。应用在后台时,某些权限的使用会受到限制。
Android 6.0–14.0 权限模型关键变化汇总
Android 版本 API 级别 关键变化 对开发者的影响
6.0 23 运行时权限模型 需动态申请危险权限
7.0–8.0 24–26 权限组概念、权限分类细化 同组权限自动授予
9.0 28 权限组归属调整 部分权限组拆分或合并
10.0 29 后台位置权限、分区存储 需单独申请后台权限
11.0 30 一次性权限、自动重置权限 权限生命周期变短
12.0 31 附近的设备权限 Wi-Fi 权限与位置解耦
13.0 33 细粒度媒体权限、通知权限 媒体访问需分类型申请
14.0 34 后台行为限制、选择性照片访问 后台权限使用更严格
一句话总结演变趋势:权限粒度越来越细、授权范围越来越小、生命周期越来越短。开发者必须主动适配,不能依赖历史行为。
Android 权限模型核心演变路径(6.0 → 14.0) Android 6.0 (API 23) 运行时权限诞生 Android 8.0 (API 26) 权限分类细化 Android 10.0 (API 29) 存储/位置拆分 Android 11.0 (API 30) 一次性权限 Android 13.0 (API 33) 细粒度媒体权限 Android 14.0 (API 34) 后台行为限制 从 6.0 到 14.0,权限模型经历了从粗放到精细的演进。 每次变化都直接影响 targetSdk 升级策略和回测方案。 安装时授权 → 运行时授权 → 细粒度授权 → 严格后台管控

4.5 升级回测中的权限适配策略

说了这么多变化,落地到实际工作中该怎么做?我总结了三条原则:

  1. 先查 targetSdk 差异:每次升级前,先列出目标版本与当前版本的权限差异列表。重点关注:新增了哪些权限、哪些权限被拆分、哪些权限改了行为。
  2. 逐权限回测:不要只测试"正常授权"路径。必须测试以下场景:
    • 用户拒绝权限
    • 用户选择"仅本次允许"
    • 用户在设置中撤销权限
    • 应用在后台被唤醒时权限已被重置
  3. 用模拟器覆盖多版本:我习惯在模拟器上跑 Android 10、11、13、14 四个版本的兼容性测试。每个版本覆盖上述所有拒绝场景。
小技巧:在模拟器中,可以通过 adb shell pm revoke <package> <permission> 快速切换权限状态,无需手动点击设置界面。这在批量回测时能省不少时间。
# 撤销某个权限(模拟用户拒绝)
adb shell pm revoke com.example.myapp android.permission.ACCESS_FINE_LOCATION

# 授予某个权限
adb shell pm grant com.example.myapp android.permission.ACCESS_FINE_LOCATION

# 查看应用当前权限状态
adb shell dumpsys package com.example.myapp | grep permission

权限适配是 targetSdk 升级中最容易出问题的环节。很多崩溃都发生在"用户拒绝权限"这个分支上。我自己每次升级都会专门列一个权限检查清单,逐条确认。

最后的提醒:不要假设低版本行为在高版本中仍然成立。Google 经常在 targetSdkVersion 提升时触发新的权限行为变更。哪怕你的 compileSdk 已经很高,只要 targetSdk 没提,新行为就不会生效。这也是为什么回测时必须把 targetSdk 调到目标版本。