赞
踩
本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新。
今天这篇文章给大家介绍一下Android 14系统中的一个新特性,对部分照片和视频进行访问授权,也可以称之为选择性照片和视频访问授权。
这是Android系统在隐私和安全性方面的又一次调整升级,目的是为了能够更好地保护用户隐私。
但是这个新特性对于开发者而言,真的是让我们苦上加苦,因此我会边吐槽边来讲解这个最新的特性。
Android开发者很苦。
我会全面介绍一下Android系统从诞生至今,在本地读写权限方面的完整变更史,然后大家应该能对Android开发者的苦感同身受。
本地读写权限指的是App拥有对手机外置公共存储空间(SD卡)读取和写入的能力。
远古时代的Android系统对于权限方面是非常宽松的。
当然这个时代的Android手机我自己都没有用过,那个时候据说对于本地读写功能是没有任何限制的,任何一个App都可以随意读写整个手机的公共存储空间。
很明显,如此宽松的权限设计是有问题的,于是Android 1.6系统引入了WRITE_EXTERNAL_STORAGE权限。
如果你想要向手机的公共存储空间写入数据,那么就得在你的应用程序的AndroidManifest.xml文件中声明这个权限才行。
Android 1.6系统只是对写入公共存储空间有了限制,读取公共存储空间的文件仍然是不受限制的。
那么从Android 4.4开始,Google引入了READ_EXTERNAL_STORAGE权限。如果想要读取公共存储空间的文件,就需要在AndroidManifest.xml文件中声明这个权限才行。
之前的Android系统,如果你想要使用某个权限,只需要在AndroidManifest.xml文件中声明一下就行。
这个声明起到什么作用呢?它会在App安装的时候告知用户,这个App总共申请了哪些权限,如果继续安装的话即视为用户同意了所有这些权限的申请。
这个规则属实有点霸王条款,因为用户无法部分同意该App申请的权限。
于是在Android 6.0系统中,Google引入了运行时权限功能,某些危险程度高的权限不能再像之前那样在AndroidManifest.xml文件中声明一下就行了,而是要在App运行的过程中弹出权限申请框,只有用户同意了授权,才能使用其权限对应的功能。
READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE都被划入了运行时权限的范畴。
关于运行时权限的更多内容,可以参考《第一行代码 Android 第3版》第8章。
运行时权限机制引入之后,Android系统的隐私和安全性达到了一个新的高度,因此也让本地读写权限在相当长的一段时间里保持了比较稳定的用法。
不过从Android 10系统开始,Google不满足于现状,又开始大刀阔斧地改革了,并且后面的改动频率让人瞠舌。
Android 10引入了Scoped Storage机制,App被禁止使用绝对路径访问公共存储空间。这样,用户设备上的隐私信息可以得到更好的保护。
而诸如手机照片、视频、音频之类的公共型资源,如果App想要访问的话,可以需要借助MediaStore API来完成。
App通过MediaStore API写入照片、视频、音频等公共型资源,是不需要申请任何权限的。而App通过MediaStore API读取照片、视频、音频等公共型资源,仍需要申请READ_EXTERNAL_STORAGE权限才行。
由于Scoped Storage机制变动过大,Google怕大量App来不及适配,因此提供了一个requestLegacyExternalStorage属性。将这个属性设置为true,那么App仍然可以使用绝对路径访问公共存储空间。
关于Android 10更多的行为变更,可以参考 Android 10适配要点,作用域存储 这篇文章。
给了一年的缓冲期,Google认为绝大部分应用应该都已经完成了Scoped Storage的适配,因此从Android 11开始requestLegacyExternalStorage属性将不再起作用。
另外,考虑到有些文件浏览器类型的App的确需要使用绝对路径访问公共存储空间,Android 11又添加了一个MANAGE_EXTERNAL_STORAGE权限,但仅限特定确实有需求的App申请,随便申请的话可能会被Google Play商店下架。
关于Android 11更多的行为变更,可以参考 Android 11新特性,Scoped Storage又有了新花样 这篇文章。
又过了两年,Google认为现有的本地读写权限又不够安全了。
具体的原因在于,本地读写权限的划分不够精细。App只需要申请READ_EXTERNAL_STORAGE权限之后,即可访问手机公共存储空间的照片、视频、音频,用户无法以更细的颗粒度对App进行授权。
于是Android 13系统废弃了READ_EXTERNAL_STORAGE权限,新增了READ_MEDIA_IMAGES、READ_MEDIA_VIDEO和READ_MEDIA_AUDIO这3个新的运行时权限,分别用于控制App对照片、视频、音频的访问。
关于Android 13更多的行为变更,可以参考 Android 13运行时权限变更一览 这篇文章。
终于到了Android 14,也就是我们本篇文章的重点了。
为了能够更好地保护用户隐私, Google在Android 14系统中新增了选择性照片和视频访问授权功能。
那么什么是选择性照片和视频访问授权呢?
在过去,当一个App申请了READ_MEDIA_IMAGES权限,如果用户选择了同意,那么该App就可以访问这台手机上所有的照片。用户是没有办法限制该App只能访问特定的某几张照片的。
而Android 14新增的这个功能则允许用户选择,是一次性授权该App访问所有的照片,还是只能访问几张特定的照片。视频也是同样的道理。
其实这个功能站在用户的角度考虑是无可厚非的,能够更好地保护用户隐私,那确实就是一个好功能。
但是站在开发者的角度,由于Android系统在本地读写权限方面的历史债太多了,如果你的代码想要考虑周全所有的场景,可能需要写得相当繁琐才行。
接下来,我会结合着一个实战Demo跟大家讲解一下如何去适配Android 14的选择性照片和视频访问授权。代码到底有多繁琐,一起来看看就知道了。
为了能够更清楚地讲解,我在文章中只会截选与选择性照片和视频访问相关的代码进行讲解。
至于Demo的完整源码,我会在文章下方给出源码的链接。
首先,Android 14推出了一个全新的运行时权限,也就是选择性照片和视频访问权限:
android.permission.READ_MEDIA_VISUAL_USER_SELECTED
我们都知道,运行时权限虽然是在App运行过程中去申请的,但仍然需要在AndroidManifest.xml中进行声明才行。
你以为在AndroidManifest.xml中声明一个权限很简单?但实际上,结合着Android的历史问题,我们需要这样写才行:
<manifest>
<!-- Devices running Android 12L (API level 32) or lower -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Devices running Android 13 (API level 33) or higher -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- To handle the reselection within the app on Android 14 (API level 34) -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
...
</manifest>
这是能够完美适配Android各个系统版本的本地读写权限的写法。
Android 12及以下系统,我们只需要声明READ_EXTERNAL_STORAGE权限即可。并且由于从Android 13开始这个权限就被废弃了,因此还要加上maxSdkVersion="32"才行。
Android 13新增了3个运行时权限,分别用于控制App对照片、视频、音频的访问。我们这个例子没有音频访问的需要,因此这里声明READ_MEDIA_IMAGES和READ_MEDIA_VIDEO这两个权限就可以了。
Android 14新增了READ_MEDIA_VISUAL_USER_SELECTED权限,用于对照片和视频进行选择性授权。
怎么样,只是在AndroidManifest.xml中声明权限是不是已经觉得相当繁琐了?
别着急,更繁琐的还在后面。
接下来是要在App运行时去请求本地读写权限,那么可想而知,不同系统版本需要请求的权限自然也是不同的。
现在Android上请求运行时权限基本上都是改用Activity Result API了,对这部分还不了解的朋友,可以参考这篇文章 Activity Result API详解,是时候放弃startActivityForResult了 。
下面我们来看一下请求本地读写权限的具体写法:
private val permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { _ -> // 处理权限请求结果 } private fun requestPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { permissionLauncher.launch( arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED) ) } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) { permissionLauncher.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO)) } else { permissionLauncher.launch(arrayOf(READ_EXTERNAL_STORAGE)) } }
这里又需要根据不同的系统版本来请求不同的权限了。
Android 14需要请求的权限是最多的,因为要适配选择性照片和视频访问功能,所以要申请READ_MEDIA_IMAGES、READ_MEDIA_VIDEO和READ_MEDIA_VISUAL_USER_SELECTED这3个权限。
Android 13系统的话可以少申请一个,只需要申请READ_MEDIA_IMAGES和READ_MEDIA_VIDEO权限就可以了。
而Android 12及以下系统最简单,申请READ_EXTERNAL_STORAGE权限即可。
这些版本判断的代码着实让人很讨厌,但是却又不得不这么写。
如果想要完全移除这些版本判断的代码,至少要等到你的minSdkVersion指定到Android 14及以上才行。但到那个时候,谁知道Google又会不会整出什么其他的花活呢。
最后,请求完本地读写权限,我们还得要判断权限的请求结果才行。而这,就更复杂了。
为了让大家不用每次都绞尽脑汁地思考这个地方应该如何处理才能考虑得最为周全,这里我直接贴出一份模板代码,大家在实现的时候复制粘贴即可:
private fun checkPermissionResult() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && (ContextCompat.checkSelfPermission(this, READ_MEDIA_IMAGES) == PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, READ_MEDIA_VIDEO) == PERMISSION_GRANTED) ) { // Android 13及以上完整照片和视频访问权限 } else if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && ContextCompat.checkSelfPermission(this, READ_MEDIA_VISUAL_USER_SELECTED) == PERMISSION_GRANTED ) { // Android 14及以上部分照片和视频访问权限 } else if (ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED) { // Android 12及以下完整本地读写访问权限 } else { // 无本地读写访问权限 } }
可以看到,现在判断权限的请求结果需要分4种情况,分别是:
我知道这很令人讨厌,但是必须要这么写,大家也别抱怨了,需要用到的时候复制拿走即可。
所以,从代码层面适配Android 14选择性照片和视频访问权限,大概就是分成了这3步。分别是在AndroidManifest.xml中进行权限声明、在程序运行时对权限进行请求、以及最后判断权限的请求结果。
但是从业务流程方面,Google给我们提出了更高的要求。
因为既然提供了选择性照片和视频访问权限,那么用户就有可能只授权了我们部分照片和视频的访问权限。而如果用户后期又想要授权我们更多照片和视频的访问权限怎么办?因此需要在UI层面给用户一个可操作的选项才行。
Google给出的最佳实践流程是这个样子的。
如图所示,当用户选择了部分照片和视频访问权限时,我们可以在界面的顶部给用户一个提示,告知下方显示的照片和视频只是用户选择授权的一部分,点击Manage按钮可以跳转到管理界面,以选择更多的照片和视频,或撤销已授权的照片和视频。
那么如何实现这套最佳实践流程呢?其实通过我刚才讲解的这几段代码示例就已经可以实现了。
下面我录制了一段最佳实践Demo的操作演示,大家可以看一看具体的流程和效果。
可以看到,如果用户选择的是允许有限权限,那么顶部始终会显示一个横幅,以方便用户管理已授权的照片和视频。
而如果用户选择了全部允许,那么顶部的横幅就会自动消失,效果如下图所示。
整个Demo的源码我都上传到了GitHub上,大家有需要可以访问下方链接进行参考:
https://github.com/guolindev/PartialAccessDemo
当然可以。毕竟这么繁琐的适配代码,我也不相信所有App都能做到完美适配。
不适配最多只是让你的App无法在Android 14系统上达到最佳的运行体验而已。
因为,Android 14还可以以兼容模式来运行没有适配选择性照片和视频访问授权的App。
至于兼容模式如何运行,Android官网写得也是相当复杂。这里我做了一下简化,方便大家理解。
兼容模式主要有以下两种情况:
第一种情况,你的App的targetSdkVersion指定在33或以下的版本,也就是说你的App还没有为Android 14系统进行适配。
这种情况就非常简单了,都还没有适配呢,Android也不会给你强上新的特性。这时会以Android 13的兼容模式来运行你的App,也就是没有选择性照片和视频访问授权这个概念。
第二种情况,你的App的targetSdkVersion指定在了34或以上的版本,但你就是没有适配选择性照片和视频访问授权这个功能。
这种情况Android 14系统仍然能正常运行,但是有几点情况是你需要了解的。
即使你没有适配选择性照片和视频访问授权功能,当你的App去请求照片和视频权限时,系统弹出的权限确认框上仍然会有“允许有限访问”这个选项。如下图所示:
如果用户选择了“允许有限访问”,也是可以像前面的录屏当中那样,选择性地对某些照片和视频进行访问授权的。
你的App会收到READ_MEDIA_IMAGES和READ_MEDIA_VIDEO这两个权限的授权回调,但仅能访问刚才用户选择的那些照片和视频。
当你的App退出后,READ_MEDIA_IMAGES和READ_MEDIA_VIDEO这两个权限的授权就会被收回,下次启动时需要重新申请,有点类似于一次性授权的效果。
下次启动时,虽然没有READ_MEDIA_IMAGES和READ_MEDIA_VIDEO权限,但是之前用户已选择的那些照片和视频,你依然有权限访问。
这套兼容规则其实也是挺绕的,这还是我整理后的简化版本,本来还要区分AndroidManifest文件中有没有声明READ_MEDIA_VISUAL_USER_SELECTED权限的,情况要更加复杂。这部分我在学习的时候也尝试理解了好久。
如果你感觉上面的兼容规则理解起来比较吃力的话,那么最推荐的做法还是抓紧去适配Android 14选择性照片和视频访问授权功能吧。
虽然适配的代码的确不怎么好写,但是复制粘贴总不难的吧?
如果想要学习Kotlin和最新的Android知识,可以参考我的新书 《第一行代码 第3版》,点击此处查看详情。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。