Android 10:如何以编程方式删除MediaStore项目及其关联的文件系统数据?

22

我正在更新我的应用程序,以使用在Android 10中引入的Scoped Storage功能。

我的应用程序与MediaStore一起工作,显示图像、视频和音频文件,并提供用户删除项目的功能。

我之前删除文件所做的操作:

  1. MediaStore.MediaColumns.DATA获取路径
  2. 使用new File(path).delete()删除该文件
  3. 手动更新MediaStore

现在,由于MediaStore.MediaColumns.DATA不可用,我迁移到使用ContentResolver.delete()从MediaStore中删除项。

例如,我有一个项的uri:content://media/external/images/media/502(它是有效的uri,我在网格中显示其缩略图)。无论是我插入这个项到MediaStore还是其他应用程序插入,都不重要。

我使用context.getContentResolver().delete(uri, null, null)。它要么成功删除(返回1行),要么捕获RecoverableSecurityException,使用startIntentSenderForResult()来访问当前uri,然后在onActivityResult()中使用相同的getContentResolver().delete()删除它并且然后成功删除。

无论如何,该项都将从MediaStore中删除,并且在查询图像的MediaStore结果中不会显示,也不会在其他应用程序中显示。
但是,这个文件仍然存在于文件系统中(使用SAF和各种文件管理器(Google文件、Total Commander)进行检查)。
有时(取决于Android版本和媒体类型),这些项目在手机重启后(或在打开Google照片后-它会扫描文件系统)会被带回MediaStore。
例如:我的Google Pixel和Google Pixel 3a XL上的Android 10针对Images/Videos/Audio的行为与上述描述相同,但Xiaomi Mi A2 Lite上的Android 9仅在Audio文件中表现出这种行为,而在删除图像/视频方面表现良好。
我在清单中有android:requestLegacyExternalStorage="false"。
有没有一种方法可以强制MediaStore也在文件系统上删除数据?为什么文件在文件系统中会被留下?
5个回答

8

是的,正如您所指出的那样,这就是我们必须删除媒体文件的方式。我们必须通过形成一个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()中获得回调结果。


1
但是我在清单文件中设置了android:requestLegacyExternalStorage="false",并重写了应用程序,以便除了Context.getFilesDir()之外,我从未使用过File。我正在为Android 11使用createDeleteRequest(),但是Android 10仍然存在我所描述的问题。 - artman
@artman,奇怪。我曾经尝试删除Android 10中图像类型文件的任何内容,从媒体存储中删除它也会删除物理副本。 (你选择在Android 10中使用作用域存储的原因是什么?在Android 10中避免使用作用域存储是一个好主意,因为在Android 10和Android 11中,作用域存储的工作方式略有不同,你可能最终需要重写大量代码) - AndroidDev
MediaStore.createDeleteRequest使用ContentResolver.delete()函数并带有权限对话框。如果您拥有MANAGE_EXTERNAL_STORAGE权限,则可以使用ContentResolver.delete()从文件系统中删除文件。 - Atakan Yildirim

6

使用此函数通过文件显示名称删除文件: 此函数将在 Android-10 或 Android-Q 中删除 MediaStore 项目及其在文件系统上的相关数据。

注意:在我的案例中,我正在处理像 MediaStore.Files.FileColumns 等文件。

public static boolean deleteFileUsingDisplayName(Context context, String displayName) {

    Uri uri = getUriFromDisplayName(context, displayName);
    if (uri != null) {
        final ContentResolver resolver = context.getContentResolver();
        String[] selectionArgsPdf = new String[]{displayName};

        try {
            resolver.delete(uri, MediaStore.Files.FileColumns.DISPLAY_NAME + "=?", selectionArgsPdf);
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
            // show some alert message
        }
    }
    return false;

}

使用此函数从DisplayName获取Uri

public static Uri getUriFromDisplayName(Context context, String displayName) {

    String[] projection;
    projection = new String[]{MediaStore.Files.FileColumns._ID};

    // TODO This will break if we have no matching item in the MediaStore.
    Cursor cursor = context.getContentResolver().query(extUri, projection,
            MediaStore.Files.FileColumns.DISPLAY_NAME + " LIKE ?", new String[]{displayName}, null);
    assert cursor != null;
    cursor.moveToFirst();

    if (cursor.getCount() > 0) {
        int columnIndex = cursor.getColumnIndex(projection[0]);
        long fileId = cursor.getLong(columnIndex);

        cursor.close();
        return Uri.parse(extUri.toString() + "/" + fileId);
    } else {
        return null;
    }

}

2
extUri 里面是什么? - superjos
1
@superjos MediaStore.Images.Media.EXTERNAL_CONTENT_URI 请将此代码片段翻译成中文。 - lincollincol
这段代码正在删除条目,但是如果已经删除,如果需要创建相同的文件,该如何实现。 - prat

2

根据我的观察,没有强制删除。

通常我会添加几个检查,以确保文件已经被删除。 在Android Q上,也不可能在没有用户手动确认每个文件的情况下删除整个相册。这使得在设备上删除文件对我来说不太有趣。


7
Android 11引入了createDeleteRequestcreateTrashRequest,用于一次性删除多个文件。虽然我需要显示多个对话框来删除一个相册,但我对此感到满意。唯一的问题是,它们会重新出现在MediaStore中。 - artman

1

我也遇到了同样的问题,但是在华为手机上,它无法处理图像和视频文件。

我发现有一些关于此问题的已报告的错误,尽管谷歌已经放弃了其中的2个,因为他们无法重现它。 我建议您加星并添加更多详细信息。

https://issuetracker.google.com/issues/157714528

https://issuetracker.google.com/issues/142270549

https://issuetracker.google.com/issues/145348304

更新:

我创建了一个新的问题票,因为一些以上问题被谷歌认为无法重现而被丢弃:

https://issuetracker.google.com/issues/184355104


0

我通过 退出作用域存储 (android:requestLegacyExternalStorage="true") 并在 Android 10 上请求 WRITE_EXTERNAL_STORAGE 来解决我的应用程序中的这个问题:

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="29"
    tools:ignore="ScopedStorage" />

然后 ContentResolver.delete 将不会触发 RecoverableSecurityException 并且还将从磁盘中删除文件。前提是您已经请求并被授予 Manifest.permission.WRITE_EXTERNAL_STORAGE 权限。

注意:如果您不关心 删除非您的应用程序创建或之前安装的应用程序创建的文件,则无需请求该权限。


不幸的是,这对我没有起作用 :(顺便说一下,我正在尝试在UI测试中完成此操作。 - Afzal N

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接