8

我正在尝试从设备的外部存储(例如/storage/emulated/0/Music文件夹中)中删除音频文件。在分析了MediaStore示例之后,我最终得到了 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 引入了 Scoped Storage,Media.DATA(和Albums.ALBUM_ART)现在已被弃用,因为应用程序可能无法访问这些文件。ContentResolver.openFileDescriptor只能用来读取文件,不能删除。

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

4

2 回答 2

15

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

选项 1:启用范围存储 ( android:requestLegacyExternalStorage="false")

如果您的应用程序添加了以 (using ) 开头的内容,contentResolver.insert那么上面的方法 (using 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”子句,但这对我来说很有意义:)

RecoverableSecurityException在 Android 10 上,如果您targetSdkVersion的 >=29 ,这可能会抛出. 在此异常中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和取消链接文件(或取消链接文件并提交重新扫描的请求MediaScannerConnection)。

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

于 2019-10-10T22:10:05.810 回答
3

Android 11中,更好、更干净的 api 支持删除/更新多个媒体文件。

Android 10之前,我们必须通过形成 File 对象来删除文件的物理副本,并使用 ContentResolver.delete() 删除 MediaStore 中的索引文件(或)对已删除的文件进行媒体扫描,这将删除它在 MediaStore 中的条目.

这就是它过去在 Android 10 操作系统下的工作方式。如果您通过在清单 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()

于 2020-11-12T08:39:20.917 回答