看了两个博客写的都不是很清楚,拿一下官方到这,定义还是官方的清晰:
管理分区外部存储访问
为了让用户更好地管理自己的文件并减少混乱,以 Android 10(API 级别 29)及更高版本为目标平台的应用在默认情况下被赋予了对外部存储设备的分区访问权限(即分区存储)。此类应用只能看到本应用专有的目录(通过 Context.getExternalFilesDir() 访问)以及特定类型的媒体。除非您的应用需要访问存放在应用的专有目录以及 MediaStore 之外的文件,否则最好使用分区存储。
下表总结了分区存储如何影响文件访问:
文件位置 | 所需权限 | 访问方法 (*) | 卸载应用时是否移除文件? |
特定于应用的目录 | 无 | 是 | |
媒体集合 (照片、视频、音频) | 访问其他应用的文件时) | 否 | |
下载内容 (文档和 电子书籍) | 无 | 存储访问框架 (加载系统的文件选择器) | 否 |
*您可以使用存储访问框架访问上表中显示的每一个位置,而无需请求任何权限。
本页介绍了使用分区存储时您的应用可以访问的文件,以及如何更新应用,使其可以继续共享、访问和修改保存在外部存储设备上的其他文件。
访问文件所需的权限
使用分区存储的应用对自己创建的文件始终拥有读/写权限,无论文件是否位于应用的专有目录内。因此,如果您的应用仅保存和访问自己创建的文件,则无需请求获得 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 权限。
不过,若要访问其他应用创建的文件,则必须满足以下两个条件:
- 您的应用已获得
READ_EXTERNAL_STORAGE
权限。 - 这些文件位于以下其中一个明确定义的媒体集合中:
- 照片:存储在 MediaStore.Images 中。
- 视频:存储在 MediaStore.Video 中。
- 音频文件:存储在 MediaStore.Audio 中。
为了访问其他应用创建的任何其他文件(包括“downloads”目录下的文件),您的应用必须使用存储访问框架,该框架允许用户选择特定文件。
如果此类应用要访问外部存储设备的原始文件系统视图,则即使拥有 READ_EXTERNAL_STORAGE
权限,也只能访问应用专有的目录。如果应用尝试通过原始文件系统视图打开此目录之外的文件,则会发生错误:
- 在托管代码中,会发生 FileNotFoundException 错误。
- 在原生代码中,会发生 EPERM 内核错误。
注意:使用分区存储的应用对于 /sdcard/DCIM/IMG1024.JPG
这类路径不具有直接内核访问权限。要访问此类文件,应用必须使用 MediaStore
,并调用 openFile() 等方法。
媒体数据限制
分区存储会施加以下媒体数据限制:
- 若您的应用未获得 ACCESS_MEDIA_LOCATION 权限,照片文件中的 Exif 元数据会被修改。要了解详情,请参阅介绍如何访问照片中的位置信息的部分。
-
MediaStore.Files
表格本身会经过过滤,仅显示照片、视频和音频文件。例如,表格中不显示 PDF 文件。 - 必须使用 MediaStore 在 Java 或 Kotlin 代码中访问媒体文件。请参阅有关如何从原生代码访问媒体文件的指南。
该指南介绍了如何处理媒体文件,并提供了有关访问 MediaStore
内的单个文档和文档树的最佳做法。如果您的应用使用分区存储,则需要使用这些方法来访问媒体。
照片中的位置信息
一些照片在其 Exif 元数据中包含位置信息,以便用户查看照片的拍摄地点。但是,由于此位置信息属于敏感信息,如果应用使用了分区存储,默认情况下 Android 10 会对应用隐藏此信息。
如果您的应用需要访问照片的位置信息,请完成以下步骤:
- 在应用的清单中请求 ACCESS_MEDIA_LOCATION 权限。
- 从
MediaStore
对象调用 setRequireOriginal(),并传入照片的 URI,如以下代码段中所示:
KOTLINJAVA Uri photoUri = Uri.withAppendedPath(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
cursor.getString(idColumnIndex));
final double[] latLong;
// Get location data from the ExifInterface class.
photoUri = MediaStore.setRequireOriginal(photoUri);
InputStream stream = getContentResolver().openInputStream(photoUri);
if (stream != null) {
ExifInterface exifInterface = new ExifInterface(stream);
double[] returnedLatLong = exifInterface.getLatLong();
// If lat/long is null, fall back to the coordinates (0, 0).
latLong = returnedLatLong != null ? returnedLatLong : new double[2];
// Don't reuse the stream associated with the instance of "ExifInterface".
stream.close();
} else {
// Failed to load the stream, so return the coordinates (0, 0).
latLong = new double[2];
}
选择停用分区存储
警告:明年,主要平台版本将要求所有应用都使用分区存储,无论应用的目标 SDK 级别是多少。因此,您应该提前确保您的应用能够使用分区存储。为此,请确保针对搭载 Android 10(API 级别 29)及更高版本的设备启用了该行为。
在您的应用完全兼容分区存储之前,您可以根据应用的目标 SDK 级别或 requestLegacyExternalStorage 清单属性,暂时选择停用分区存储:
- 以 Android 9(API 级别 28)或更低版本为目标平台。
- 如果以 Android 10 或更高版本为目标平台,请在应用的清单文件中将
requestLegacyExternalStorage
的值设为true
: <manifest ... > <!-- This attribute is "false" by default on apps targeting Android 10 or higher. --> <application android:requestLegacyExternalStorage="true" ... > ... </application> </manifest>
注意:如果某个应用在安装时启用了传统外部存储,则该应用会保持此模式,直到卸载为止。无论设备后续是否升级为搭载 Android 10 或更高版本,或者应用后续是否更新为以 Android 10 或更高版本为目标平台,此兼容性行为均适用。
要测试以 Android 9 或更低版本为目标平台的应用在使用分区存储时的行为,您可以通过将 requestLegacyExternalStorage
的值设为 false
来选择启用该行为。