Android 14 引入了“所选照片访问权限”,让用户可以向应用授予 访问媒体库中的特定图片和视频,而不是授予 对给定类型的所有媒体的访问权限。
仅当您的应用以 Android 14(API 级别 34)或 。如果您还没有使用照片选择器,我们建议您在 您的应用,在用户选择图片和视频时提供一致的体验 无需请求任何存储空间,即可加强对用户隐私的保护 权限。
如果您使用存储权限来维护自己的图库选择器,并且需要
全面掌控您的实现、调整实现
使用新的 READ_MEDIA_VISUAL_USER_SELECTED
权限。如果您的应用
未使用新权限,则系统会在兼容模式下运行您的应用
模式。
目标 SDK 版本 | 已声明READ_MEDIA_VISUAL_USER_SELECTED |
已启用所选照片访问权限 | 用户体验行为 |
---|---|---|---|
SDK 33 | 否 | 否 | 不适用 |
是 | 是 | 由应用控制 | |
SDK 34 | 否 | 是 | 由系统控制(兼容行为) |
是 | 是 | 由应用控制 |
创建或调整您自己的图库选择器
创建您自己的图库选择器需要进行大量开发和维护, 并且您的应用需要请求存储权限以征得用户的明确同意。 用户可以拒绝这些请求;或者,如果您的应用在安装了 Android 14 且您的应用以 Android 14(API 级别 34)或更高版本为目标平台,限制 访问所选媒体。下图显示了 然后使用新选项选择媒体。
<ph type="x-smartling-placeholder">本部分介绍了创建您自己的图库的推荐方法
使用 MediaStore
。如果您已为应用维护图库选择器
并且需要保持完全的控制权,您可以使用这些示例来调整您的
实施。如果您没有更新实现来处理选定项目
Google 相册访问权限,则系统会在兼容模式下运行您的应用。
请求权限
首先,在 Android 清单中请求正确的存储权限, 具体取决于操作系统版本:
<!-- Devices running Android 12L (API level 32) or lower -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Devices running Android 13 (API level 33) or higher -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- To handle the reselection within the app on devices running Android 14
or higher if your app targets Android 14 (API level 34) or higher. -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
然后,根据操作系统版本请求正确的运行时权限:
// Register ActivityResult handler
val requestPermissions = registerForActivityResult(RequestMultiplePermissions()) { results ->
// Handle permission requests results
// See the permission example in the Android platform samples: https://github.com/android/platform-samples
}
// Permission request logic
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO))
} else {
requestPermissions.launch(arrayOf(READ_EXTERNAL_STORAGE))
}
部分应用不需要权限
从 Android 10(API 级别 29)开始,应用不再需要存储权限即可添加
将文件保存到共享存储空间。也就是说,应用可以向图库添加图片,
录制视频并保存到共享存储空间,或下载 PDF 账单,无需
必须请求存储权限如果您的应用仅向共享文件添加文件
且不查询图片或视频,则应停止请求存储空间
权限并在 AndroidManifest.xml
中将 maxSdkVersion
设置为 API 28:
<!-- No permission is needed to add files to shared storage on Android 10 (API level 29) or higher -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
处理媒体重新选择
使用 Android 14 中的所选照片访问权限功能时,您的应用应采用
用于控制媒体的新 READ_MEDIA_VISUAL_USER_SELECTED
权限
重新选择,并更新应用界面,以允许用户授予应用访问权限
一组不同的图片和视频下图显示了
请求权限并重新选择媒体:
打开选择对话框时,系统会根据不同的条件显示照片、视频或
您请求的权限例如,如果您要请求
READ_MEDIA_VIDEO
权限(无需 READ_MEDIA_IMAGES
权限),
视频会显示在界面中供用户选择文件。
// Allow the user to select only videos
requestPermissions.launch(arrayOf(READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))
您可以查看您的应用是完全、部分还是拒绝访问设备
并相应更新界面。请求这些权限
当应用需要存储空间访问权限时,而不是在启动时。请注意,
可以在“onStart
”和“onResume
”应用之间更改授予的权限
因为用户可以在设置更改访问权限后
关闭您的应用。
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
(
ContextCompat.checkSelfPermission(context, READ_MEDIA_IMAGES) == PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(context, READ_MEDIA_VIDEO) == PERMISSION_GRANTED
)
) {
// Full access on Android 13 (API level 33) or higher
} else if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
ContextCompat.checkSelfPermission(context, READ_MEDIA_VISUAL_USER_SELECTED) == PERMISSION_GRANTED
) {
// Partial access on Android 14 (API level 34) or higher
} else if (ContextCompat.checkSelfPermission(context, READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
// Full access up to Android 12 (API level 32)
} else {
// Access denied
}
查询设备库
在确认您拥有适当的存储权限后,您可以
与 MediaStore
交互以查询设备库(相同的方法
授予的访问权限是部分访问权限还是完整访问权限):
data class Media(
val uri: Uri,
val name: String,
val size: Long,
val mimeType: String,
)
// Run the querying logic in a coroutine outside of the main thread to keep the app responsive.
// Keep in mind that this code snippet is querying only images of the shared storage.
suspend fun getImages(contentResolver: ContentResolver): List<Media> = withContext(Dispatchers.IO) {
val projection = arrayOf(
Images.Media._ID,
Images.Media.DISPLAY_NAME,
Images.Media.SIZE,
Images.Media.MIME_TYPE,
)
val collectionUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Query all the device storage volumes instead of the primary only
Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
Images.Media.EXTERNAL_CONTENT_URI
}
val images = mutableListOf<Media>()
contentResolver.query(
collectionUri,
projection,
null,
null,
"${Images.Media.DATE_ADDED} DESC"
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(Images.Media._ID)
val displayNameColumn = cursor.getColumnIndexOrThrow(Images.Media.DISPLAY_NAME)
val sizeColumn = cursor.getColumnIndexOrThrow(Images.Media.SIZE)
val mimeTypeColumn = cursor.getColumnIndexOrThrow(Images.Media.MIME_TYPE)
while (cursor.moveToNext()) {
val uri = ContentUris.withAppendedId(collectionUri, cursor.getLong(idColumn))
val name = cursor.getString(displayNameColumn)
val size = cursor.getLong(sizeColumn)
val mimeType = cursor.getString(mimeTypeColumn)
val image = Media(uri, name, size, mimeType)
images.add(image)
}
}
return@withContext images
}
我们简化了此代码段,以说明如何与 MediaStore
互动。
在可用于生产环境的应用中,使用 Paging
库有助于确保获得良好性能。
查询上次的选择
搭载 Android 15 及更高版本、搭载 Android 14 且支持 Google Play 系统更新的应用可以
查询用户上次在部分访问模式下制作的图片和视频
启用 QUERY_ARG_LATEST_SELECTION_ONLY
:
if (getExtensionVersion(Build.VERSION_CODES.U) >= 12) {
val queryArgs = bundleOf(
QUERY_ARG_SQL_SORT_ORDER to "${Images.Media.DATE_ADDED} DESC"
QUERY_ARG_LATEST_SELECTION_ONLY to true
)
contentResolver.query(collectionUri, projection, queryArgs, null)
}
升级设备后,照片和视频的访问权限将保留
如果您的应用安装在从较低 Android 版本升级到 Android 14 的设备上,系统会保留对用户照片和视频的完整访问权限,并自动向您的应用授予一些权限。确切行为取决于在设备升级到 Android 14 之前向您的应用授予的一组权限。
Android 13 的权限
请考虑以下情况:
- 您的应用安装在搭载 Android 13 的设备上。
- 用户已向您的应用授予
READ_MEDIA_IMAGES
和READ_MEDIA_VIDEO
权限。 - 然后,当您的应用仍保持安装状态时,设备升级到 Android 14。
- 您的应用开始以 Android 14(API 级别 34)或更高版本为目标平台。
在此情况下,您的应用仍然拥有对用户照片和视频的完整访问权限。系统还会保留已自动授予您的应用的 READ_MEDIA_IMAGES
权限和 READ_MEDIA_VIDEO
权限。
Android 12 及更低版本的权限
请考虑以下情况:
- 您的应用安装在搭载 Android 13 的设备上。
- 用户已向您的应用授予
READ_EXTERNAL_STORAGE
权限或WRITE_EXTERNAL_STORAGE
权限。 - 然后,当您的应用仍保持安装状态时,设备升级到 Android 14。
- 您的应用开始以 Android 14(API 级别 34)或更高版本为目标平台。
在此情况下,您的应用仍然拥有对用户照片和视频的完整访问权限。系统还会自动向您的应用授予 READ_MEDIA_IMAGES
权限和 READ_MEDIA_VIDEO
权限。
最佳做法
本部分包含使用
READ_MEDIA_VISUAL_USER_SELECTED
权限。如需了解更多信息,请查看
我们的权限最佳做法。
请勿永久存储权限状态
请勿永久存储权限状态(包括 SharedPreferences
或 DataStore
)。存储的状态可能与实际状态不同步。重置权限后,权限状态可能会发生变化。
应用休眠、用户发起的应用设置更改,或者
您的应用会进入后台请改用以下方法来检查存储权限:
ContextCompat.checkSelfPermission()
。
不要假定拥有对照片和视频的完整访问权限
根据 Android 14 中引入的更改,您的应用可能只有部分
访问设备的照片库。如果应用在使用 ContentResolver
进行查询时缓存了 MediaStore
数据,则缓存可能不是最新的。
- 应始终使用
ContentResolver
查询MediaStore
,而不是依赖于存储的缓存。 - 当应用在前台运行时,将结果保存在内存中。
- 在应用完成
onResume
应用生命周期时刷新结果 因为用户可能会通过 权限设置。
将 URI 访问权限视为临时访问权限
如果用户在系统权限中选择了选择照片和视频
对话框,应用对所选照片和视频的访问权限最终会过期。
您的应用应始终处理无法访问任何 Uri
、
它们的权威性至关重要
按权限过滤可供选择的媒体类型
选择对话框受请求的权限类型的影响:
- 仅请求
READ_MEDIA_IMAGES
仅显示可供选择的图片。 - 仅请求
READ_MEDIA_VIDEO
仅显示可供选择的视频。 - 同时请求
READ_MEDIA_IMAGES
和READ_MEDIA_VIDEO
会显示完整的 可供选择
您应该根据应用的用例请求
以免用户体验不佳。如果某项特征仅预期
要选择的视频,请确保仅请求 READ_MEDIA_VIDEO
个。
在单次操作中请求权限
如需阻止用户看到多个系统运行时对话框,请在单次操作中请求 READ_MEDIA_VISUAL_USER_SELECTED
、ACCESS_MEDIA_LOCATION
和“读取媒体”权限(READ_MEDIA_IMAGES
和/或 READ_MEDIA_VIDEO
)。
允许用户管理自己的选择
当用户选择部分访问模式时,您的应用不应假设 设备的照片库为空,应该允许用户授予更多权限 文件。
用户可能会决定通过 权限设置,而不授予对某些可视媒体文件的访问权限。
兼容模式
如果您还使用存储权限来维护自己的图库选择器,
调整您的应用以使用新的 READ_MEDIA_VISUAL_USER_SELECTED
权限后,无论何时用户何时使用您的应用,系统都会在兼容模式下运行您的应用
需要选择或重新选择媒体。
初始媒体选择期间的行为
在初始选择过程中,如果用户选择“选择照片和视频”(请参阅
图 1),则 READ_MEDIA_IMAGES
和 READ_MEDIA_VIDEO
权限如下所示:
授予临时权限
临时访问用户选择的照片和视频。当应用迁移到
或当用户主动终止您的应用时,系统最终
拒绝授予这些权限。此行为就像其他单次授权一样。
重新选择媒体期间的行为
如果您的应用日后需要访问其他照片和视频,您可以
必须手动请求 READ_MEDIA_IMAGES
权限或
READ_MEDIA_VIDEO
权限。系统遵循的流程与使用
初始权限请求,提示用户选择照片和视频(请参阅
图 2)。
如果您的应用遵循权限最佳实践,则此变更不应
导致您的应用无法正常运作如果您的应用并未假定使用
保留访问权限,存储系统权限状态,或刷新
显示的图片。不过,此行为
是理想之选。帮助提供最佳体验
我们建议您实现照片选择器或根据
应用的图库选择器,以便直接使用
READ_MEDIA_VISUAL_USER_SELECTED
权限。