我正在参加「启航方案」

在 Android 体系最近的几个大版别里,更新方向有很大一部分都集中在了隐私安全这一方面,每个版别都会新增隐私安全约束,或许是对之前的隐私项进行进一步的升级

  • Android 10。分区存储、约束拜访不可重置的硬件标识符、约束对剪贴板数据的拜访权限
  • Android 11。强制执行分区存储、单次授权、主动重置权限、软件包可见性
  • Android 12。颁发大致方位信息权限、剪贴板拜访告诉、更安全的组件导出
  • Android 13。细化的媒体权限、内置图片挑选器、隐藏剪贴板中的灵敏内容、屏蔽不匹配的 Intent、针对 Wifi 设备的新运行时权限、广告 ID 权限

Android 13 在最近也发布了正式版,此次版别中新增的隐私安全约束也总算能够解决许多运用长久以来的两个问题了

在许多 Android 运用中,都会经过内置一个图片挑选器来向用户展现体系相册内的一切图片,常见于“上传用户头像、发送图片”等事务场景,这就需求经过获得 READ_EXTERNAL_STORAGE 权限来完成了。而这个权限也存在极大的隐私危险,运用或许会向用户说明该权限只是只会在挑选图片时运用,但除了运用开发者外,谁又能保证运用不会依靠该权限在后台偷偷做些什么呢?而关于开发者来说也属于无法之举,运用或许只是只是想拿到体系相册内的图片而已,却因为 Android 体系的机制被迫要去恳求一个运用范围更高的权限

平心而论,我觉得在运用中内置一个图片挑选器确实算作是一个比较能提高用户体会的点。因为 Android 体系内置的图片挑选器的功用长久以来一直很弱,运用的事务场景往往都需求约束图片的类型、允许挑选多张图片并约束最大挑选数,但体系却无法满足这些需求,假如咱们比及用户挑选图片回来后再提示用户不支撑该图片格式,或许是让用户多次往复挑选图片的话,那确实是挺让人反感的

以上两个问题,依靠在 Android 13 中新增的两个隐私安全项:细化的媒体权限内置图片挑选器,也总算能够得到解决了,后边来一一进行解说

细化的媒体权限

在 Android 13 之前,运用假如想要拜访设备中的媒体资源的话,都有必要经过 READ_EXTERNAL_STORAGE 权限才干完成。从 Android 13 开端,体系将 READ_EXTERNAL_STORAGE 细分为了三个愈加明确的权限,分别用于拜访用户的三类媒体资源:Image、Video、Audio,然后让用户能够按需授权,避免隐私危险无序扩大

媒体类型 恳求权限
图片 READ_MEDIA_IMAGES
视频 READ_MEDIA_VIDEO
音频 READ_MEDIA_AUDIO

咱们需求一起经过 运用的 targetSdkVersion设备的体系版别 来适配这三个权限

  • 假如运用还未适配 Android 13,也即 targetSdkVersion 小于 33

    • 此刻不论体系版别是多少,依然仍是经过 READ_EXTERNAL_STORAGE 权限来拜访媒体资源
  • 假如运用已适配 Android 13,也即 targetSdkVersion 大于等于 33

    • 假如体系版别小于 33,此刻依然要经过 READ_EXTERNAL_STORAGE 才干拜访媒体资源
    • 假如体系版别大于等于 33,此刻有必要经过这三个细分权限才干拜访媒体资源,READ_EXTERNAL_STORAGE 权限已失效

简略来说,假如体系版别和 targetSdkVersion 都大于等于 33 的话,此刻就有必要经过这三个细分权限才干拜访媒体资源,其它情况仍是需求依赖于 READ_EXTERNAL_STORAGE 权限

看个实际的比如

几个月前我发布了一个开源库, 一个用 Jetpack Compose 完成的 Android 图片挑选结构:Matisse

也宣布了一篇文章进行介绍:Jetpack Compose 完成一个图片挑选结构

Android 13 媒体权限适配指南

Matisse 的特色和优势有:

  • 彻底用 Kotlin 完成,回绝 Java
  • UI 层彻底用 Jetpack Compose 完成,回绝原生 View 体系
  • 支撑精细自定义主题,默认供给了 日间 和 夜间 两种主题
  • 支撑精准筛选图片类型,只显示想要的图片类型
  • 支撑在图片列表页敞开摄影进口,一起支撑 FileProvider 和 MediaStore 两种摄影策略
  • 支撑详细获取图片信息,总共包含 uri、displayName、mimeType、width、height、orientation、size、path、bucketId、bucketDisplayName 等十个特点值
  • 适配到 Android 12

当然,目前 Android 13 已发布正式版,这阵子我也将 Matisse 适配到了 Android 13

Matisse 作为一个开源的图片挑选结构,天然需求一起顾及 引证方的 targetSdkVersion设备的体系版别 这两个变量,针对 Android 13 的 READ_MEDIA_IMAGES 权限,其实也仅需求将以前固定恳求 READ_EXTERNAL_STORAGE 权限的方法,改为挑选性恳求即可


private fun requestReadImagesPermission() {
  val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
    applicationInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU
   ) {
    Manifest.permission.READ_MEDIA_IMAGES
   } else {
    Manifest.permission.READ_EXTERNAL_STORAGE
   }
  if (PermissionUtils.checkSelfPermission(context = this, permission = permission)) {
    //已获得必要权限,能够去加载体系相册图片了
   } else {
    //去恳求必要权限
   }
}

此外,咱们也要依据实际情况来声明最合适且最少的权限

假如运用的 targetSdkVersion 小于 33,则仍是持续声明 READ_EXTERNAL_STORAGE 权限即可

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

假如运用的 targetSdkVersion 大于等于 33,则需求一起声明 READ_EXTERNAL_STORAGE 和 READ_MEDIA_IMAGES 两个权限。此外,在这种情况下,READ_EXTERNAL_STORAGE 权限已无法用于 Android 13 开端之后的体系版别了,所以能够将此权限的 maxSdkVersion 设为 32,然后不会出现在 Android 13 开端之后的体系版别中

<uses-permission
  android:name="android.permission.READ_EXTERNAL_STORAGE"
  android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

内置图片挑选器

从 Android 13 开端,Android 体系内置了一个功用更为强大的图片挑选器,能够让运用愈加灵敏地拜访设备中的媒体资源,且无需拥有查看设备上一切媒体文件的权限。此外,虽然官方将其命名为图片挑选器,但实际上也支撑挑选设备中的视频文件

引证 Google 官方的描述:

相片挑选器供给了一个可阅读、可搜索的界面,其中按日期从最近到最早的顺序向用户出现其媒体库中的文件。此东西为用户供给了一种安全的内置图片和视频挑选方法,让其无需向运用颁发对整个媒体库的拜访权限

假如您允许体系配置相片挑选器,则该东西适用于满足以下条件的设备(Android Go 设备在外):

  • 搭载 Android 11(API 等级 30)或更高版别
  • 经过 Google 体系更新接纳对模块化体系组件的更改

此外,假如您允许体系配置相片挑选器,该东西会主动更新,并跟着时间推移为运用的用户供给扩展的功用,而无需对代码进行任何更改

再来看下运用如何来运用该图片挑选器

首要,因为 Google 会经过 Google Play 将该新型的图片挑选器推送给 Android 11 及以上体系的设备 (不包括 Go 设备),所以该功用不只是只能在 Android 13 开端后的体系能够运用,咱们能够经过如下方法来查看当时设备是否支撑该功用

//compileSdkVersion 需求至少为 33 才能够调用此方法
fun isPhotoPickerAvailable(): Boolean {
  return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    true
   } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    getExtensionVersion(Build.VERSION_CODES.R) >= 2
   } else {
    false
   }
}

咱们能够经过以下两种 ActivityResultContract 来发动图片挑选器:

  • PickVisualMedia。用于挑选单张图片或单个视频
  • PickMultipleVisualMedia。用于挑选多张图片或多个视频

例如,在只需求挑选单张图片或许单个视频的情况下,能够这么运用:

private val pickMedia = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
  if (uri != null) {
    //TODO
   }
}
​
//挑选图片或视频
pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo))
​
//仅挑选图片
pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
​
//仅挑选视频
pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly))
​
//仅挑选 Gif 图片
val mimeType = "image/gif"
pickMedia.launch(
  PickVisualMediaRequest(
    ActivityResultContracts.PickVisualMedia.SingleMimeType(
      mimeType
     )
   )
)

在这种情况下,图片挑选器会以半屏方式翻开

Android 13 媒体权限适配指南

类似的,假如想要挑选多张图片或许是多个视频,能够经过 PickMultipleVisualMedia 来限制最大的选取数量

private val pickMultipleMedia = registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(5)) { uris ->
  if (uris.isNotEmpty()) {
    //TODO
   }
}

此刻,图片挑选器会以全屏展开的方式进行展现,当资源数量超限时也会对用户进行提示

Android 13 媒体权限适配指南

需求留意,以上代码需求增加 1.6.0 或更高版别的 androidx.activity 库后才能够运用。此外,从 PickVisualMedia 和 PickMultipleVisualMedia 的源码能够看到,Android 13 内置的图片挑选器对应的是 MediaStore.ACTION_PICK_IMAGES 这个新增的 Intent,而假如当时设备不支撑媒体挑选器功用的话,就会改为经过调用 Intent.ACTION_OPEN_DOCUMENT 来挑选媒体资源,这种情况下 PickMultipleVisualMedia 设定的数量上限天然也就失效了

结束

Android 13 细化的媒体权限解决了 READ_EXTERNAL_STORAGE 带来的隐私危险无序扩大的问题,让用户能够按需授权。而内置的图片挑选器又能够让运用在无需用户授权的情况下就能够灵敏地选取媒体资源,运用也就彻底没有必要自己内置一个图片挑选器了

所以说,平衡用户体会和用户隐私安全最好的做法应该是:

  • 在 Android 13 之前依然仍是经过运用内置的图片挑选器来完成事务功用
  • 在 Android 13 开端之后的版别仅运用体系内置的图片挑选器,彻底弃用 READ_EXTERNAL_STORAGE 权限

此次 Android 13 版别仍是有点东西的 ~