为了让用户更好地控制自己的文件并减少混乱,Android 10 针对应用推出了一种新的存储范例,称为分区存储。分区存储改变了应用在设备的外部存储设备中存储和访问文件的方式。为了帮助您迁移应用以支持分区存储,请遵循本指南中有关常见存储用例的最佳做法。这些用例分为两类:处理媒体文件和处理非媒体文件。
如需详细了解如何在 Android 平台中存储和访问文件,请参阅存储培训指南。
处理媒体文件
本部分介绍了处理媒体文件(视频、图片和音频文件)的一些常见用例,并概要说明了应用可以使用的方法。下表对其中每个用例进行了总结,并列出了包含更多详细介绍的各个部分的链接。
用例 | 摘要 |
---|---|
显示所有图片或视频文件 | 在所有 Android 版本中均使用相同的方法。 |
显示特定文件夹中的图片或视频 | 在所有 Android 版本中均使用相同的方法。 |
访问照片中的位置信息 | 如果应用使用分区存储,请使用一种方法。如果应用停用分区存储,请使用不同方法。 |
在一次操作中修改或删除多个媒体文件 | 在 Android 11 中,请使用一种方法。在 Android 10 中,请停用分区存储并改用适用于 Android 9 及更低版本的方法。 |
导入已经存在的单张图片 | 在所有 Android 版本中均使用相同的方法。 |
拍摄单张图片 | 在所有 Android 版本中均使用相同的方法。 |
与其他应用共享媒体文件 | 在所有 Android 版本中均使用相同的方法。 |
与特定应用共享媒体文件 | 在所有 Android 版本中均使用相同的方法。 |
使用直接文件路径访问代码或库中的文件 | 在 Android 11 中,请使用一种方法。在 Android 10 中,请停用分区存储并改用适用于 Android 9 及更低版本的方法。 |
显示多个文件夹中的图片或视频文件
使用 query()
API 查询媒体集合。如需对媒体文件进行过滤或排序,请调整 projection
、selection
、selectionArgs
和 sortOrder
参数。
显示特定文件夹中的图片或视频
请使用以下方法:
- 按照请求应用权限中所述的最佳做法,请求
READ_EXTERNAL_STORAGE
权限。 - 根据
MediaColumns.DATA
的值检索媒体文件,该值包含磁盘上的媒体项的绝对文件系统路径。
访问照片中的位置信息
如果应用使用分区存储,请按照媒体存储指南的照片中的位置信息部分的步骤操作。
在一次操作中修改或删除多个媒体文件
根据应用在哪个 Android 版本上运行来纳入逻辑。
在 Android 11 上运行
请使用以下方法:
- 使用
MediaStore.createWriteRequest()
或MediaStore.createTrashRequest()
为应用的写入或删除请求创建待定 intent,然后通过调用该 intent 提示用户授予修改一组文件的权限。 评估用户的响应:
- 如果授予了权限,请继续修改或删除操作。
- 如果未授予权限,请向用户说明您的应用中的功能为何需要该权限。
详细了解如何使用 Android 11 中提供的这些方法执行批量操作。
在 Android 10 上运行
如果您的应用以 Android 10(API 级别 29)为目标平台,请停用分区存储,继续使用适用于 Android 9 及更低版本的方法来执行此操作。
在 Android 9 或更低版本上运行
请使用以下方法:
- 按照请求应用权限中所述的最佳做法,请求
WRITE_EXTERNAL_STORAGE
权限。 - 使用
MediaStore
API 修改或删除媒体文件。
导入已经存在的单张图片
当您要导入已经存在的单张图片(例如,用作用户个人资料的照片)时,应用可以将自己的界面用于此操作,也可以使用系统选择器。
提供您自己的界面
请使用以下方法:
- 按照请求应用权限中所述的最佳做法,请求
READ_EXTERNAL_STORAGE
权限。 - 使用
query()
API 查询媒体集合。 - 在应用的自定义界面中显示结果。
使用系统选择器
使用 ACTION_GET_CONTENT
intent,它会要求用户选择要导入的图片。
如果您想过滤系统选择器提供给用户选择的图片类型,您可以使用 setType()
或 EXTRA_MIME_TYPES
。
拍摄单张图片
当您想拍摄单张图片在应用中使用(例如,用作用户个人资料的照片)时,请使用 ACTION_IMAGE_CAPTURE
intent 要求用户使用设备的摄像头拍照。系统会将拍摄的照片存储在 MediaStore.Images
表中。
与其他应用共享媒体文件
使用 insert()
方法将记录直接添加到 MediaStore 中。如需了解详情,请参阅媒体存储指南的添加项目部分。
与特定应用共享媒体文件
按照设置文件共享指南中所述,使用 Android FileProvider
组件。
从代码或依赖库中使用直接文件路径访问文件
根据应用在哪个 Android 版本上运行来纳入逻辑。
在 Android 11 上运行
请使用以下方法:
- 按照请求应用权限中所述的最佳做法,请求
READ_EXTERNAL_STORAGE
权限。 - 使用直接文件路径访问文件。
如需了解详情,请参阅使用原始路径访问文件。
在 Android 10 上运行
如果您的应用以 Android 10(API 级别 29)为目标平台,请停用分区存储,继续使用适用于 Android 9 及更低版本的方法来执行此操作。
在 Android 9 或更低版本上运行
请使用以下方法:
- 按照请求应用权限中所述的最佳做法,请求
WRITE_EXTERNAL_STORAGE
权限。 - 使用直接文件路径访问文件。
处理非媒体文件
本部分介绍了处理非媒体文件的一些常见用例,并概要说明了应用可以使用的方法。下表对其中每个用例进行了总结,并列出了包含更多详细介绍的各个部分的链接。
用例 | 摘要 |
---|---|
打开文档文件 | 在所有 Android 版本中均使用相同的方法。 |
从旧版存储位置迁移现有文件 | 请尽可能将文件迁移到分区存储。在 Android 10 中,请根据需要停用分区存储。 |
与其他应用共享内容 | 在所有 Android 版本中均使用相同的方法。 |
缓存非媒体文件 | 在所有 Android 版本中均使用相同的方法。 |
打开文档文件
使用 ACTION_OPEN_DOCUMENT
intent 要求用户使用系统选择器选择要打开的文件。如果您想过滤系统选择器提供给用户选择的文件类型,您可以使用 setType()
或 EXTRA_MIME_TYPES
。
例如,您可以使用以下代码查找所有 PDF、ODT 和 TXT 文件:
Kotlin
startActivityForResult( Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "*/*" putExtra(Intent.EXTRA_MIME_TYPES, arrayOf( "application/pdf", // .pdf "application/vnd.oasis.opendocument.text", // .odt "text/plain" // .txt )) }, REQUEST_CODE )
Java
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] { "application/pdf", // .pdf "application/vnd.oasis.opendocument.text", // .odt "text/plain" // .txt }); startActivityForResult(intent, REQUEST_CODE);
从旧版存储位置迁移现有文件
如果目录不是应用专属目录或公开共享目录,则被视为旧版存储位置。如果您的应用要在旧版存储位置中创建文件或使用其中的文件,我们建议您将应用的文件迁移到可通过分区存储进行访问的位置,并对应用进行必要的更改以使用分区存储中的文件。
保留对旧版存储位置的访问权限以进行数据迁移
您的应用需要保留对旧版存储位置的访问权限,才能将任何应用文件迁移到可通过分区存储进行访问的位置。您应该使用的方法取决于应用的目标 API 级别。
如果应用以 Android 11 为目标平台
使用
preserveLegacyExternalStorage
标记保留旧版存储模型,以便在用户升级到以 Android 11 为目标平台的新版应用时,应用可以迁移用户的数据。继续停用分区存储,以便您的应用可以继续在搭载 Android 10 的设备上访问旧版存储位置中的文件。
如果应用以 Android 10 为目标平台
停用分区存储,更轻松地在不同 Android 版本之间保持应用行为不变。
迁移应用数据
当应用准备就绪,可以迁移时,请使用以下方法:
- 检查应用的工作文件是否位于
/sdcard/
目录或其任何子目录中。 - 将任何私有应用文件从
/sdcard/
下的当前位置移至getExternalFilesDir()
方法所返回的目录。 - 将任何共享的非媒体文件从
/sdcard/
下的当前位置移至Downloads/
目录的应用专用子目录。 - 从
/sdcard/
目录中移除应用的旧存储目录。
与其他应用共享内容
如需与一个其他应用共享应用的文件,请使用 FileProvider
。对于全部需要在彼此之间共享文件的应用,我们建议您对每个应用使用内容提供程序,然后在将应用添加到集合中时同步数据。
缓存非媒体文件
您应该使用的方法取决于您需要缓存的文件类型。
- 小文件或包含敏感信息的文件:请使用
Context#getCacheDir()
。 - 大型文件或不含敏感信息的文件:请使用
Context#getExternalCacheDir()
。
暂时停用分区存储
在您的应用与分区存储完全兼容之前,您可以使用以下方法之一暂时停用分区存储:
- 以 Android 9(API 级别 28)或更低版本为目标平台。
如果您以 Android 10(API 级别 29)或更高版本为目标平台,请在应用的清单文件中将
requestLegacyExternalStorage
的值设置为true
:<manifest ... > <!-- This attribute is "false" by default on apps targeting Android 10 or higher. --> <application android:requestLegacyExternalStorage="true" ... > ... </application> </manifest>
如需测试以 Android 9 或更低版本为目标平台的应用在使用分区存储时的行为,您可以通过将 requestLegacyExternalStorage
的值设置为 false
,选择启用该行为。如果在搭载 Android 11 的设备上进行测试,您还可以使用应用兼容性标记来测试应用在使用和不使用分区存储时的行为。