Scoped Storage:如何通过 MediaStore 删除多个音频文件?

Scoped Storage: how to delete multiple audio files via MediaStore?

我正在尝试从设备的外部存储(例如 /storage/emulated/0/Music 文件夹中)删除音频文件。 在分析了 MediaStore sample 之后,我针对 API 28 及更早版本得出了以下解决方案:

fun deleteTracks(trackIds: LongArray): Int {
    val whereClause = buildWildcardInClause(trackIds.size) // _id IN (?, ?, ?, ...)
    return resolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, Array(trackIds.size) { trackIds[it].toString() })
}

我注意到上面的代码只从 MediaStore 数据库中删除曲目,而不是从设备中删除曲目(文件在重新启动后重新添加到 MediaStore 中)。所以我修改了该代码以查询 Media.DATA 列,然后使用该信息删除相关文件。这按预期工作。

但现在 Android Q 引入了分区存储,Media.DATA(和 Albums.ALBUM_ART)现已弃用,因为该应用程序可能无法访问这些文件。 ContentResolver.openFileDescriptor只能读取文件,不能删除文件

那么从 Android Q 开始,推荐的删除曲目的方法是什么? 该示例未显示如何从 MediaStore 中删除多个文件,并且 MediaStore.Audio.Media 似乎与 MediaStore.Images.Media.

的工作方式不同

在Android10中有两种方法可以做到这一点,这取决于应用程序AndroidManifest.xml文件中android:requestLegacyExternalStorage的值。

选项 1: 分区存储已启用(android:requestLegacyExternalStorage="false"

如果您的应用添加了开头的内容(使用contentResolver.insert),那么上述方法(使用contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, idsArray))应该有效。在 Android 10 日,应用程序提交到媒体商店的任何内容都可以由该应用程序自由操作。

对于用户添加的内容,或者属于另一个应用程序的内容,过程要复杂一些。

要从媒体商店中删除单个项目,请不要将通用媒体商店 URI 传递给 delete(即:MediaStore.Audio.Media.EXTERNAL_CONTENT_URI),但是相反,使用项目的特定 URI。

context.contentResolver.delete(
    audioContentUri,
    "${MediaStore.Audio.Media._ID} = ?",
    arrayOf(audioContentId)

(我很确定 'where' 子句没有必要,但它对我来说很有意义:)

在 Android 10 上,如果你的 targetSdkVersion >=29,这可能会抛出一个 RecoverableSecurityException。在这个异常中是一个 IntentSender,它可以由你的 Activity 启动,以请求用户允许修改或删除它。 (发件人位于recoverableSecurityException.userAction.actionIntent.intentSender

由于用户必须对每个项目授予权限,因此无法批量删除它们。 (或者,可能会请求修改或删除整个专辑的权限,一次一首歌曲,然后使用上面使用的 delete 查询。此时您的应用程序将有权删除它们,并且Media Store 会一次完成所有这些操作。但是您必须先请求每首歌曲。)

选项 2: 启用传统存储(android:requestLegacyExternalStorage="true"

在这种情况下,只需调用 contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, idsArray) 不仅会从数据库中删除项目,还会删除文件。

该应用需要持有 WRITE_EXTERNAL_STORAGE 权限才能正常工作,但我测试了问题中的代码并确认文件也已删除。

因此,对于较旧的 API 版本,我认为您需要使用 contentResolver.delete 并自行取消链接文件(或取消链接文件并通过 [=28 提交重新扫描请求) =]).

对于 Android 10+,contentResolver.delete 将删除索引和内容本身。

Android 11 deleting/updating 中,更好更清洁的 API 支持多个媒体文件。

Android10之前,我们必须通过创建文件对象来删除文件的物理副本,并使用[=31=删除MediaStore中的索引文件]()(或)对已删除的文件进行媒体扫描,这将删除它在 MediaStore 中的条目。

这就是它在 Android 10 os 以下的工作方式。如果您通过在清单 android:requestLegacyExternalStorage="true"

中指定它来选择退出范围存储,并且在 Android 10 中仍然可以正常工作

现在 Android 11 您被迫使用分区存储。如果您想删除任何非您创建的媒体文件,您必须获得用户的许可。您可以使用 MediaStore.createDeleteRequest() 获得权限。这将通过描述用户将要执行的操作来显示一个对话框,一旦授予权限,android 就会有一个内部代码来负责删除物理文件和 MediaStore 中的条目。

private void requestDeletePermission(List<Uri> uriList){
  if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
      PendingIntent pi = MediaStore.createDeleteRequest(mActivity.getContentResolver(), uriList);

      try {
         startIntentSenderForResult(pi.getIntentSender(), REQUEST_PERM_DELETE, null, 0, 0,
                  0);
      } catch (SendIntentException e) { }
    }
}

上面的代码会同时执行这两种操作,请求删除权限,并在授予权限后删除文件。

你会在 onActivityResult() 中得到结果