授予照片和视频部分访问权限

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) 或更高版本为目标,则可以将访问权限限制为选定的媒体。下图显示了请求权限和使用新选项选择媒体的示例。

The .
图 1. 新对话框允许用户选择他们想要向您的应用提供的特定照片和视频,此外还有授予完全访问权限或拒绝所有访问权限的常规选项。

本节演示了使用 MediaStore 创建您自己的图库选择器的推荐方法。如果您已经维护了应用的图库选择器并需要保持完全控制,您可以使用这些示例来调整您的实现。如果您不更新您的实现以处理“选择照片访问权限”,系统将以兼容模式运行您的应用。

请求权限

首先,根据操作系统版本,在 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 权限来控制媒体重新选择,并更新应用的界面以允许用户授予您的应用访问不同图片和视频集的权限。下图显示了请求权限和重新选择媒体的示例

The .
图 2. 新对话框还允许用户重新选择他们想要向您的应用提供的照片和视频。

打开选择对话框时,会根据请求的权限显示照片、视频或两者。例如,如果您请求 READ_MEDIA_VIDEO 权限而没有 READ_MEDIA_IMAGES 权限,则用户界面中将只显示视频供用户选择文件。

// Allow the user to select only videos
requestPermissions.launch(arrayOf(READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))

您可以检查您的应用是否对设备照片库拥有完全、部分或拒绝的访问权限,并相应地更新您的界面。在应用需要存储访问权限时(而不是在启动时)请求这些权限。请记住,权限授予可能会在 onStartonResume 应用生命周期回调之间发生更改,因为用户可以在不关闭应用的情况下更改设置中的访问权限。

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 library,以帮助确保良好的性能。

查询上次选择

Android 15+ 设备以及支持 Google Play 系统更新的 Android 14 设备可以通过启用 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 中的权限

考虑以下情况

  1. 您的应用安装在运行 Android 13 的设备上。
  2. 用户已授予您的应用 READ_MEDIA_IMAGES 权限和 READ_MEDIA_VIDEO 权限。
  3. 设备随后在您的应用仍安装的情况下升级到 Android 14。
  4. 您的应用开始以 Android 14 (API 级别 34) 或更高版本为目标。

在这种情况下,您的应用仍然对用户的照片和视频拥有完全访问权限。系统还会自动将 READ_MEDIA_IMAGESREAD_MEDIA_VIDEO 权限授予您的应用。

Android 12 及更低版本中的权限

考虑以下情况

  1. 您的应用安装在运行 Android 13 的设备上。
  2. 用户已授予您的应用 READ_EXTERNAL_STORAGE 权限或 WRITE_EXTERNAL_STORAGE 权限。
  3. 设备随后在您的应用仍安装的情况下升级到 Android 14。
  4. 您的应用开始以 Android 14 (API 级别 34) 或更高版本为目标。

在这种情况下,您的应用仍然对用户的照片和视频拥有完全访问权限。系统还会自动将 READ_MEDIA_IMAGES 权限和 READ_MEDIA_VIDEO 权限授予您的应用。

最佳实践

本节包含使用 READ_MEDIA_VISUAL_USER_SELECTED 权限的几项最佳实践。有关更多信息,请查阅我们的权限最佳实践

不要永久存储权限状态

不要以永久方式存储权限状态,包括 SharedPreferencesDataStore。存储的状态可能与实际状态不同步。权限状态可能会在权限重置应用休眠、用户在应用设置中发起更改或应用进入后台时发生更改。相反,请使用 ContextCompat.checkSelfPermission() 检查存储权限。

不要假定对照片和视频有完全访问权限

根据 Android 14 中引入的更改,您的应用可能仅对设备的照片库拥有部分访问权限。如果应用在使用 ContentResolver 查询 MediaStore 数据时进行缓存,则缓存可能不是最新的。

  • 始终使用 ContentResolver 查询 MediaStore,而不是依赖存储的缓存。
  • 在您的应用处于前台时,将结果保存在内存中。
  • 当您的应用通过 onResume 应用生命周期时刷新结果,因为用户可能会通过权限设置从完全访问切换到部分访问。

将 URI 访问视为临时

如果用户在系统权限对话框中选择“选择照片和视频”,您的应用对所选照片和视频的访问权限最终会过期。您的应用应始终处理无法访问任何 Uri 的情况,无论其权限如何。

按权限过滤可选媒体类型

选择对话框对请求的权限类型敏感

  • 仅请求 READ_MEDIA_IMAGES 只显示可选择的图片。
  • 仅请求 READ_MEDIA_VIDEO 只显示可选择的视频。
  • 同时请求 READ_MEDIA_IMAGESREAD_MEDIA_VIDEO 显示整个照片库供选择。

根据您应用的用例,您应确保请求正确的权限,以避免糟糕的用户体验。如果某项功能仅期望选择视频,请确保仅请求 READ_MEDIA_VIDEO

在单个操作中请求权限

为防止用户看到多个系统运行时对话框,请在单个操作中请求 READ_MEDIA_VISUAL_USER_SELECTEDACCESS_MEDIA_LOCATION 和“读取媒体”权限(READ_MEDIA_IMAGESREAD_MEDIA_VIDEO 或两者)。

允许用户管理他们的选择

当用户选择部分访问模式时,您的应用不应假定设备的照片库为空,并且应允许用户授予更多文件。

用户可能决定通过权限设置从完全访问切换到部分访问,而不授予对某些视觉媒体文件的访问权限。

兼容模式

如果您使用存储权限维护自己的图库选择器,但尚未调整您的应用以使用新的 READ_MEDIA_VISUAL_USER_SELECTED 权限,则每当用户需要选择或重新选择媒体时,系统都会以兼容模式运行您的应用。

初始媒体选择期间的行为

在初始选择期间,如果用户选择“选择照片和视频”(参见图 1),则在应用会话期间授予 READ_MEDIA_IMAGESREAD_MEDIA_VIDEO 权限,从而提供临时权限授予和对用户选择的照片和视频的临时访问。当您的应用移至后台或用户主动终止您的应用时,系统最终会拒绝这些权限。此行为与其他一次性权限类似。

媒体重新选择期间的行为

如果您的应用稍后需要访问其他照片和视频,您必须手动再次请求 READ_MEDIA_IMAGES 权限或 READ_MEDIA_VIDEO 权限。系统遵循与初始权限请求相同的流程,提示用户选择照片和视频(参见图 2)。

如果您的应用遵循权限最佳实践,此更改不应破坏您的应用。如果您的应用不假定 URI 访问已保留、不存储系统权限状态或在权限更改后刷新显示图片集,则更是如此。然而,根据您应用的用例,此行为可能不理想。为了帮助为您的用户提供最佳体验,我们建议实现照片选择器调整应用的图库选择器以直接使用 READ_MEDIA_VISUAL_USER_SELECTED 权限处理此行为。