Android 11 进一步增强了平台功能,为外部存储设备上的应用和用户数据提供了更好的保护。预览版引入了多项去年在 Android 开发者峰会上宣布的增强功能,例如可主动选择启用的媒体原始文件路径访问机制、面向媒体的批量修改操作,以及存储访问框架的界面更新。
为方便过渡到使用分区存储,该平台为开发者引入了进一步的改进。如需详细了解如何将应用迁移到根据应用的用例使用分区存储,请参阅本页面的分区存储部分,以及 Android 存储用例和最佳做法指南。
分区存储强制执行
为了给开发者更多时间进行测试,以 Android 10(API 级别 29)为目标平台的应用仍可请求 requestLegacyExternalStorage 属性。应用可以利用此标记暂时停用与分区存储相关的变更,例如授予对不同目录和不同类型的媒体文件的访问权限。当您将应用更新为以 Android 11 为目标平台后,系统会忽略 requestLegacyExternalStorage
标记。
保持与 Android 10 的兼容性
如果应用在 Android 10 设备上运行时选择退出分区存储,建议您继续在应用的清单文件中将 requestLegacyExternalStorage
设为 true
。这样,应用就可以在运行 Android 10 的设备上继续按预期运行。
将数据迁移到使用分区存储时可见的目录
如果您的应用使用旧版存储模型且之前以 Android 10 或更低版本为目标平台,您可能会将数据存储到启用分区存储模型后您的应用无法访问的目录中。在以 Android 11 为目标平台之前,请将数据迁移到与分区存储兼容的目录。在大多数情况下,您可以将数据迁移到您的应用专用目录。
如果您有需要迁移的数据,当用户升级到以 Android 11 为目标平台的新版应用时,可以保留旧版存储模型。这样,用户就可以保留对您的应用之前用于保存数据的目录中存储的应用数据的访问权限。如需启用旧版存储模型以进行升级,请在应用的清单中将 preserveLegacyExternalStorage 属性设为 true
。
注意:大多数应用都不需要使用 preserveLegacyExternalStorage
。此标记仅适用于这样一种情况:您将应用数据迁移到了与分区存储兼容的位置,并且希望用户在更新您的应用时保留对数据的访问权限。使用此标记会导致更难以测试分区存储对您应用的用户有何影响,因为当用户更新您的应用时,它会继续使用旧版存储模型。
如果您使用 preserveLegacyExternalStorage
,旧版存储模型只在用户卸载您的应用之前保持有效。如果用户在搭载 Android 11 的设备上安装或重新安装您的应用,那么无论 preserveLegacyExternalStorage
的值是什么,您的应用都无法停用分区存储模型。
测试分区存储
如需在您的应用中启用分区存储,而不考虑应用的目标 SDK 版本和清单标记值,请启用以下应用兼容性标记:
- DEFAULT_SCOPED_STORAGE(默认情况下,对所有应用处于启用状态)
- FORCE_ENABLE_SCOPED_STORAGE(默认情况下,对所有应用处于停用状态)
如需停用分区存储而改用旧版存储模型,请取消设置这两个标记。
管理设备存储空间
在 Android 11 上,使用分区存储模型的应用只能访问自身的应用专用缓存文件。如果您的应用需要管理设备存储空间,请执行以下操作:
- 通过调用 ACTION_MANAGE_STORAGE intent 操作检查可用空间。
- 如果设备上的可用空间不足,请提示用户同意让您的应用清除所有缓存。为此,请调用 ACTION_CLEAR_APP_CACHE intent 操作。
注意:ACTION_CLEAR_APP_CACHE
intent 操作会严重影响设备的电池续航时间,并且可能会从设备上移除大量的文件。
外部存储设备上的应用专用目录
在 Android 11 上,应用无法在外部存储设备上创建自己的应用专用目录。如需访问系统为您的应用提供的目录,请调用 getExternalFilesDirs()。
媒体文件访问权限
为了在保证用户隐私的同时可以更轻松地访问媒体,Android 11 增加了以下功能。
执行批量操作
为实现各种设备之间的一致性并增加用户便利性,Android 11 向 MediaStore API 中添加了多种方法。对于希望简化特定媒体文件更改流程(例如在原位置编辑照片)的应用而言,这些方法尤为有用。
添加的方法如下:
用户向应用授予对指定媒体文件组的写入访问权限的请求。
用户将设备上指定的媒体文件标记为“收藏”的请求。对该文件具有读取访问权限的任何应用都可以看到用户已将该文件标记为“收藏”。
用户将指定的媒体文件放入设备垃圾箱的请求。垃圾箱中的内容会在系统定义的时间段后被永久删除。
注意:如果您的应用是设备 OEM 的预安装图库应用,您可以将文件放入垃圾箱而不显示对话框。如需执行该操作,请直接将 IS_TRASHED 设置为 1
。
用户立即永久删除指定的媒体文件(而不是先将其放入垃圾箱)的请求。
系统在调用以上任何一个方法后,会构建一个 PendingIntent 对象。应用调用此 intent 后,用户会看到一个对话框,请求用户同意应用更新或删除指定的媒体文件。
例如,以下是构建 createWriteRequest()
调用的方法:
List<Uri> urisToModify = /* A collection of content URIs to modify. */
PendingIntent editPendingIntent = MediaStore.createWriteRequest(contentResolver,
urisToModify);
// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.getIntentSender(),
EDIT_REQUEST_CODE, null, 0, 0, 0);
评估用户的响应,然后继续操作,或者在用户不同意时向用户说明您的应用为何需要获取权限:
@Override
protected void onActivityResult(int requestCode, int resultCode,
@Nullable Intent data) {
...
if (requestCode == EDIT_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
/* Edit request granted; proceed. */
} else {
/* Edit request not granted; explain to the user. */
}
}
}
您可以对 createFavoriteRequest()、createTrashRequest() 和 createDeleteRequest() 使用相同的通用模式。
使用直接文件路径和原生库访问文件
为了帮助您的应用更顺畅地使用第三方媒体库,Android 11 允许您通过以下两种方式访问归因于您的应用的媒体文件:
- File API。
- 原生库,例如
fopen()
。
如果您的应用具有 READ_EXTERNAL_STORAGE 权限,则可以访问所有媒体文件,无论这些文件是否归因于您的应用。
如果您使用 File
API 或原生库访问媒体文件,建议您在应用的清单文件中将 requestLegacyExternalStorage设置为 true
,以选择停用分区存储。这样,您的应用就可以在搭载 Android 10 的设备上正常工作。
注意:使用 File
API 或原生库保存媒体文件时,应用的性能会略有下降。请尽可能改用 MediaStore API 以访问共享存储空间中的媒体文件。
访问其他应用的私有目录
在 Android 11 上,应用无法再访问外部存储设备中的任何其他应用的专用于特定应用的目录中的文件。
文档访问限制
为让开发者有时间进行测试,以下与存储访问框架 (SAF) 相关的变更只有在应用以 Android 11 为目标平台时才会生效。
访问目录
您无法再使用 ACTION_OPEN_DOCUMENT_TREE intent 操作请求访问以下目录:
- 内部存储卷的根目录。
- 设备制造商认为可靠的各个 SD 卡卷的根目录,无论该卡是模拟卡还是可移除的卡。可靠的卷是指应用在大多数情况下可以成功访问的卷。
-
Download
目录。
访问文件
您无法再使用 ACTION_OPEN_DOCUMENT_TREE
或 ACTION_OPEN_DOCUMENT intent 操作请求用户从以下目录中选择单独的文件:
-
Android/data/
目录及其所有子目录。 -
Android/obb/
目录及其所有子目录。
测试变更
如需测试此行为更改,请执行以下操作:
- 通过
ACTION_OPEN_DOCUMENT
操作调用 intent。检查Android/data/
和Android/obb/
目录是否均不显示。 - 执行以下某项操作:
- 启用 RESTRICT_STORAGE_ACCESS_FRAMEWORK 应用兼容性标记。
- 以 Android 11 为目标平台。
- 通过
ACTION_OPEN_DOCUMENT_TREE
操作调用 intent。检查Download
目录是否已显示,以及与目录关联的操作按钮是否呈灰显状态。
权限
Android 11 引入了与存储权限相关的以下变更。
以任何版本为目标平台
图 1. 应用使用分区存储并请求 READ_EXTERNAL_STORAGE
权限时显示的对话框。
不管应用的目标 SDK 版本是什么,以下变更均会在 Android 11 中生效:
- 存储运行时权限已重命名为文件和媒体。
- 如果您的应用未停用分区存储并且请求 READ_EXTERNAL_STORAGE 权限,用户会看到不同于 Android 10 的对话框。该对话框表明您的应用正在请求访问照片和媒体,如图 1 所示。
用户可以在系统设置中查看哪些应用具有READ_EXTERNAL_STORAGE
权限。在设置 > 隐私 > 权限管理器 > 文件和媒体页面上,具有该权限的每个应用都列在允许存储所有文件下。
注意:如果您的应用以 Android 11 为目标平台,请记住,对“所有文件”的这种访问权限是只读访问权限。如需使用此应用读取和写入共享的存储空间中的所有文件,需要具有所有文件访问权限。
以 Android 11 为目标平台
如果应用以 Android 11 为目标平台,那么 WRITE_EXTERNAL_STORAGE
权限和 WRITE_MEDIA_STORAGE
特许权限将不再提供任何其他访问权限。
请注意,在搭载 Android 10(API 级别 29)或更高版本的设备上,您的应用可以提供明确定义的媒体集合,例如 MediaStore.Downloads
,而无需请求任何存储相关权限。详细了解如何在处理应用中的媒体文件时仅请求必要的权限。
所有文件访问权限
某些应用的核心用例需要访问大量的文件,如文件管理操作或备份和恢复操作。这些应用可通过执行以下操作获取“所有文件访问权限”:
- 声明 MANAGE_EXTERNAL_STORAGE 权限。
- 使用 ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent 操作将用户引导至一个系统设置页面,在该页面上,用户可以为您的应用启用以下选项:授予所有文件的管理权限。
如需确定您的应用是否已获得所有文件访问权限,请调用 Environment.isExternalStorageManager()。
注意:MANAGE_EXTERNAL_STORAGE
权限允许应用访问共享存储空间中的潜在敏感数据。在即将发布的政策公告中,Google Play 会为需要此权限的应用提供相关指南,敬请期待。
“所有文件访问权限”可授予以下权限:
- 对共享存储空间中的所有文件的读写访问权限。
注意:/sdcard/Android/media
目录是共享存储空间的一部分。 - 对 MediaStore.Files 表的内容的访问权限。
- 对 USB On-The-Go (OTG) 驱动器和 SD 卡的根目录的访问权限。
- 除
/Android/data/
、/sdcard/Android
和/sdcard/Android
的大多数子目录外,对所有内部存储目录的写入权限。此写入权限包括文件路径访问权限。
获得此权限的应用仍然无法访问属于其他应用的应用专用目录,因为这些目录在存储卷上显示为Android/data/
的子目录。
当应用具有“所有文件访问权限”时,它可以使用 MediaStore API 或文件路径访问这些额外的文件和目录。但是,当您使用存储访问框架时,只有在不具备“所有文件访问权限”的情况下才能访问文件或目录。
如需详细了解此特殊权限以及 Android 11 中 Android 存储 API 的其他更改,请参阅标题为 Android 上的现代用户存储的媒体文章。