从 Android Q 测试版 1 开始,此变更具有以下属性:
为了让用户更好地控制自己的文件,并限制文件混乱情况,Android Q 更改了应用访问设备外部存储空间中文件的方式。Android Q 用更精细的媒体特定权限替换了 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE权限,并且无需特定权限,应用即可访问自己在外部存储设备上的文件。这些变更会影响您的应用在外部存储设备中保存和访问文件的方式。
本指南介绍了如何更新应用,以使其可以继续共享、访问和更新保存在外部存储设备上的文件,还提供了兼容性注意事项,并说明了如何激活此行为变更。
Android Q 在外部存储设备中为每个应用提供了一个“隔离存储沙盒”(例如 /sdcard)。任何其他应用都无法直接访问您应用的沙盒文件。由于文件是您应用的私有文件,因此您不再需要任何权限即可在外部存储设备中访问和保存自己的文件。此变更可让您更轻松地保证用户文件的隐私性,并有助于减少应用所需的权限数量。
注意:如果用户卸载了您的应用,系统就会清理隔离存储沙盒中的文件。
在外部存储设备中存储文件的最佳位置是 Context.getExternalFilesDir() 返回的位置,因为此位置的行为方式在所有 Android 版本中都保持一致。使用此方法时,请在媒体环境中传递与您要创建或打开的文件类型对应的文件。例如,要访问或保存应用私有图片,请调用 Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)。
如果您的应用创建了属于相应用户的文件,并且希望在卸载该应用时保留此用户,则将这些文件保存到某个通用媒体集合(也称为“共享集合”)中。共享集合包括:照片和视频、音乐和下载内容。
您的应用无需请求任何权限即可在这些共享集合中创建和修改自己的文件。但是,如果您的应用需要创建和修改其他应用已创建的文件,则必须先请求相应的权限:
注意:未设置用于访问下载内容共享集合的权限。您的应用可以访问此集合中自己的文件。但是,要访问此集合中其他应用的文件,您必须允许用户使用系统的文件选择器应用来选择文件。
注意:如果您的应用使用存储访问框架,则无需请求这些媒体作用域权限。
详细了解如何使用其他应用的文件。
在请求必要的权限后,您的应用会使用 MediaStore API 访问这些集合:
注意:对于 Android Q 上新安装的应用,对 getExternalStoragePublicDirectory() 的调用仅会提供对应用存储在其隔离存储沙盒中的文件的访问权限。要始终拥有对其他应用的文件的访问权限,请更新应用的逻辑以改用 MediaStore。
默认情况下,当用户卸载您的应用时,Android Q 会清理您保存到沙盒中的文件。要在卸载应用时保留这些文件,请使用存储访问框架,或将文件保存到共享集合中。
要保留共享集合中的文件,请在相关的 MediaStore 集合中新插入一行,并用以下方法填充此行对应的列:
插入此行后,您可以使用 ContentResolver.openFileDescriptor() 之类的 API 读取新建文件的数据或向其中写入数据。
但是,如果用户稍后重新安装了您的应用,该应用将无法访问这些文件,除非它执行以下操作之一:
这种情况类似于一个应用尝试访问另一个应用的文件的情况。
Android Q 新增了多项增强功能,让用户可以更好地控制在外部存储设备中访问照片的方式。
一些照片在其 Exif 元数据中包含位置信息,以便用户查看照片的拍摄地点。由于此位置信息很敏感,因此默认情况下 Android Q 会对该信息进行遮盖。这种对位置信息的限制与适用于相机特性的限制不同。
注意:如果您的应用是用户的默认照片管理器应用,则平台会自动让您的应用访问照片中的位置信息。
如果您的应用需要访问照片的位置信息,请完成以下步骤:
如果您的应用是相机应用,则它无法直接访问保存在照片和视频共享集合中的照片(除非它是设备的默认照片管理器应用)。要引导用户找到图库应用,请使用 ACTION_REVIEW intent。
本节介绍了您的应用如何与其他应用存储在共享集合中的文件进行互动。
要访问并读取其他应用已保存到外部存储设备中的媒体文件,请完成以下步骤:
注意:ContentResolver 类包含一个新方法 loadThumbnail(),它可为您的应用提供文件预览。最好先调用 loadThumbnail(),以便用户可以查看媒体文件的快照,无需让您的应用自己加载所有文件。此方法还支持更灵活的请求,例如请求特定尺寸以及取消请求的功能。
通过将文件保存到共享集合,您的应用会成为该文件的所有者。通常,只有当您是共享集合中某个文件的所有者时,您的应用才能向该文件写入数据。但是,如果为您的应用分配了正确的角色,您还可以向其他应用拥有的文件写入数据:
注意:无论您的应用是默认的照片管理器应用还是默认的音乐应用,它都应保持正常运行。
要修改其他应用最初保存到外部存储设备中的媒体文件,请完成下列步骤之一:
在某些用例中,您的应用可能需要打开或创建它无权访问的文件:
对于这些情况,请使用存储访问框架(该框架允许用户选择要打开的特定文件,或选择特定位置来保存文件)。
如果您要管理一套需要相互访问彼此文件的应用,请使用 content:// URI(我们已将其作为安全最佳做法推荐)。
如需了解详情,请参阅有关如何设置文件共享的文档。
升级设备上之前安装过的应用的兼容性模式
对访问外部存储设备中文件的限制仅适用于以 Android Q 为目标平台的应用,或者在运行 Android Q 的设备上新安装的应用。
当满足以下每个条件时,系统会将您应用的文件访问权限置于兼容性模式:
当您的应用处于兼容性模式时,以下文件访问行为适用:
在首次卸载您的应用之前,此兼容性模式一直有效。
注意:如果稍后在同一设备上重新安装您的应用,则不会重新激活兼容性模式。
在 Android 9(API 级别 28)及更低版本中,所有存储设备上的所有文件都显示在单个 "external" 卷名称下。Android Q 为每个外部存储设备提供唯一的卷名称。此命名系统可帮助您高效地整理内容并将内容编入索引,还可让您控制新内容的存储位置。
注意:主外部存储设备始终使用卷名称 "external"。
要唯一标识外部存储设备中的特定文件,您必须同时使用卷名称和 ID。例如,主存储设备上的文件是 content://media/external/images/media/12,而名为 FA23-3E92 的辅助存储设备上的对应文件是 content://media/FA23-3E92/images/media/12。
您可以通过将此卷名称传递到特定媒体集合(例如 MediaStore.Images.getContentUri())中来访问存储在特定卷中的文件。
要获取所有当前可用卷的名称列表,请调用 MediaStore.getAllVolumeNames(),如以下代码段所示:
val volumeNames: Set
在没有可移动外部存储设备的设备上,使用以下命令启用虚拟磁盘以进行测试:
adb shell sm set-virtual-disk true
为了让您的应用与此新行为变更兼容,平台提供了多种方法来调整与此变更相关联的多个参数。
要在 Android Q 测试版 1 中启用此行为变更,请在终端窗口中执行以下命令:
adb shell sm set-isolated-storage on
运行此命令后,设备将重启。如果设备未重启,请稍等片刻,然后再尝试重新运行此命令。
要确认行为变更是否已生效,请使用以下命令:
adb shell getprop sys.isolated_storage_snapshot
测试兼容性模式行为
测试您的应用时,您可以通过在终端窗口中运行以下命令来为外部文件存储访问权限启用兼容性模式:
adb shell cmd appops set your-package-name android:legacy_storage allow
要停用兼容性模式,请在 Android Q 上卸载然后重新安装您的应用,或在终端窗口中运行以下命令:
adb shell cmd appops set your-package-name android:legacy_storage default
要以文件管理器的形式广泛访问外部存储设备中的目录,请使用 ACTION_OPEN_DOCUMENT_TREE intent。有关示例,请参阅 GitHub 上的 android-DirectorySelection 示例。
注意:在 Android Q 中,StorageVolume 类中的 createAccessIntent() 方法已被弃用,因此您不应使用此方法浏览外部存储设备。如果您使用此方法浏览外部存储设备,运行 Android Q 设备的用户将无法在您的应用中查看保存在外部存储设备中的文件。