7、权限模型演变:从Marshmallow运行时权限到Android 13细粒度媒体权限,升级中的权限适配

权限这个话题,每次Android大版本升级都会让我头疼一阵子。说实话,从Android 6.0到现在,权限模型已经改了好几轮了。每次改动都意味着我们这些做系统升级的人,得重新审视一遍代码。

今天我就把这几年的权限演变脉络理一理。你想想看,从最早的「安装时授权」到现在的「运行时申请」,再到Android 13的细粒度媒体权限,这条路其实挺曲折的。

7.1 运行时权限:一个分水岭

Android 6.0(Marshmallow)引入运行时权限,这是个标志性事件。在那之前,用户安装应用时看到一长串权限列表,基本就是「要么全给,要么别装」。

我记得当时有个项目,客户要求把老款平板从Android 5.1升级到7.1。那会儿我们团队最头疼的就是权限适配。老应用里到处都是READ_CONTACTSACCESS_FINE_LOCATION这种敏感权限,升级后直接崩溃。

核心变化:
  • 权限被分为「正常权限」和「危险权限」两类
  • 危险权限必须在运行时动态申请
  • 用户可以选择「拒绝」或「不再询问」

适配的关键点其实就一个:别假设权限已经有了。每次使用敏感API前,都得检查一下权限状态。

// 检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {
    // 申请权限
    ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.CAMERA},
            REQUEST_CAMERA_CODE);
} else {
    // 已有权限,直接操作
    openCamera();
}

嗯,这里要注意一点:requestPermissions是异步的,结果会回调到onRequestPermissionsResult里。我见过不少新手直接在回调外面继续执行代码,结果权限还没拿到就崩了。

7.2 Android 10-11:分区存储与权限收紧

到了Android 10(Q),Google开始搞分区存储(Scoped Storage)。说白了,就是不让应用随便访问外部存储了。每个应用只能读写自己的专属目录,或者通过MediaStore访问公共媒体文件。

我在项目中遇到过一个问题:某款文件管理器升级到Android 10后,用户反馈「看不到SD卡里的文件了」。查了半天,发现是READ_EXTERNAL_STORAGE权限在分区存储模式下,只能读到媒体文件,其他文件类型根本看不到。

我的建议:如果应用需要访问非媒体文件,可以考虑用MANAGE_EXTERNAL_STORAGE权限(Android 11引入)。但这个权限审核很严,Google Play上架时得说明用途。

Android 11进一步收紧了权限。比如「一次性权限」——用户可以选择「仅本次允许」,下次再用还得重新申请。还有「自动重置权限」——如果应用长时间没用,系统会自动撤销它的权限。

7.3 Android 13:细粒度媒体权限

Android 13(Tiramisu)把媒体权限拆得更细了。以前一个READ_EXTERNAL_STORAGE就能读所有媒体文件,现在不行了。

旧权限 新权限(Android 13) 说明
READ_EXTERNAL_STORAGE READ_MEDIA_IMAGES 只读图片
READ_EXTERNAL_STORAGE READ_MEDIA_VIDEO 只读视频
READ_EXTERNAL_STORAGE READ_MEDIA_AUDIO 只读音频

为什么要这么改?说白了,用户隐私保护越来越严格。以前你申请一个权限就能看所有照片、视频、音乐,现在用户可以选择「只给看照片」,视频和音乐不给看。

适配的时候,我建议这样处理:

// Android 13 适配示例
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    // 分别申请三种媒体权限
    String[] permissions = {
        Manifest.permission.READ_MEDIA_IMAGES,
        Manifest.permission.READ_MEDIA_VIDEO,
        Manifest.permission.READ_MEDIA_AUDIO
    };
    requestPermissions(permissions, REQUEST_MEDIA_CODE);
} else {
    // 低版本用旧权限
    requestPermissions(
        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
        REQUEST_STORAGE_CODE
    );
}
注意:如果应用targetSdkVersion低于33,系统会帮你把READ_EXTERNAL_STORAGE映射成三个新权限。但Google Play从2023年开始要求新应用必须target 33+,所以别指望这个兼容机制能一直用。

7.4 权限适配的避坑指南

我曾经踩过一个坑:某款相机应用在Android 13上拍照后无法保存图片。查了半天,发现是只申请了CAMERA权限,没申请WRITE_EXTERNAL_STORAGE。但在Android 13上,WRITE_EXTERNAL_STORAGE已经没用了,得用MediaStore的API来保存。

这里我总结几个常见问题:

  • 权限被拒绝后怎么办?别直接退出应用。可以引导用户去设置里手动开启,或者降级功能(比如没有定位权限就不显示地图,但其他功能正常用)。
  • 「不再询问」的处理:如果用户勾选了「不再询问」,shouldShowRequestPermissionRationale会返回false。这时候再弹申请对话框也没用,得跳转到系统设置页面。
  • 权限组的概念:同一个权限组里的权限,只要有一个被授予,其他也会自动授予。比如CAMERARECORD_AUDIO同属MICROPHONE组,申请相机权限时,麦克风权限也会被带出来。

7.5 知识体系总览

下面这张图是我自己整理的权限模型演变路线,从Android 6.0到13,每个版本的关键变化一目了然:

Android 权限模型演变路线(6.0 → 13) 6.0 运行时权限 动态申请 危险/正常分类 8.0 权限组细化 电话权限拆分 安装未知应用 10 分区存储 Scoped Storage MediaStore 11 一次性权限 自动重置 MANAGE_EXTERNAL_STORAGE 13 细粒度媒体权限 图片/视频/音频 分权限申请 适配要点 • 每次使用敏感API前检查权限状态 • 区分 targetSdkVersion 做条件判断 • 处理「拒绝」和「不再询问」两种场景 • Android 13 上使用 READ_MEDIA_* 替代 READ_EXTERNAL_STORAGE • 权限被拒后提供降级方案,不要直接崩溃 • 利用 shouldShowRequestPermissionRationale 引导用户

从这张图可以看出来,Google的权限策略是越来越严的。每次升级,我们都要检查一遍应用里用到的所有权限,看看有没有被废弃或者拆分的。

最后说一句:权限适配没有捷径。老老实实把每个权限的申请流程走一遍,该检查的检查,该降级的降级。别想着偷懒,用户隐私保护这条红线,碰不得。


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