通过 Intent.ACTION_VIEW 从 SD 卡打开文件

Opening file from SD card via Intent.ACTION_VIEW

我知道我们可以像这样从内部存储正常打开文件:

File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "my_file.zip");
Uri uri = (FileProvider.getUriForFile(context, AUTHORITY_OPEN_FILE, file)
Intent intent = new Intent(Intent.ACTION_VIEW)
            .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            .setData(uri);
context.startActivity(intent);

但我不知道如何从 SD 卡打开文件,我们可以使用 DocumentFile 像这样选择:

Uri uri = new Uri.Builder()
            .scheme("content")
            .authority("com.android.externalstorage.documents")
            .appendPath("document")
            .appendPath(directory)
            .appendPath(fileName)
            .build();
DocumentFile documentFile = DocumentFile.fromSingleUri(context, uri);

我尝试了以下代码段:

Uri uri = documentFile.getUri();
Intent intent = new Intent(Intent.ACTION_VIEW)
            .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            .setData(uri);
context.startActivity(intent);

但结果错误:

 Caused by: java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.VIEW dat=content://com.android.externalstorage.documents/document/6331-6132:/haxm-windows_r05_2.zip flg=0x10000001 cmp=com.google.android.gm/.browse.TrampolineActivity } from ProcessRecord{a0ed6d5 9894:com.mypackage.app/u0a169} (pid=9894, uid=10169) requires com.google.android.gm.permission.READ_GMAIL

我授予读写外部存储权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

还有Uri权限:

int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
context.getContentResolver().takePersistableUriPermission(uri, takeFlags);

存储在 SD 卡中的文件给我们一个 Uri 像这样,其中 6331-6132 是我们可移动 SD 卡的标识符:

content://com.android.externalstorage.documents/document/6331-6132:Folder/haxm-windows_r05_2.zip

我在 Whosebug 上阅读了很多帖子,但没有任何帮助。你能帮我解决这个错误吗?提前致谢。

这可能是非常基本的,但错误消息说您缺少权限

你问的是 READ_EXTERNAL_STORAGE 吗?

https://developer.android.com/training/data-storage/files#ExternalStoragePermissions

你必须在清单文件中添加权限,对于 marshmallow 或更高版本的 android 你添加 运行 时间权限。 对于此视图

https://www.simplifiedcoding.net/android-marshmallow-permissions-example/

I get the Uri using Intent.ACTION_OPEN_DOCUMENT. Next, I save the Uri into my database. Finally, I reconstruct the Uri to get stream using getContentResolver().openInputStream(uri).

我的猜测是,在将 Uri 保存到数据库之前,您没有在 onActivityResult() 中的 ContentResolver 上调用 takePersistableUriPermission()。没有这个,you do not have long-term access to the content identified by the Uri.

我终于找到了解决办法。当您使用 DocumentFile.fromSingleUri(context, uri) 打开外部文件时将抛出 java.lang.SecurityException: Permission Denial。此静态方法仅当您在 onActivityResult() 方法中选择文件时才有用,操作为 Intent.ACTION_OPEN_DOCUMENTIntent.ACTION_CREATE_DOCUMENT。当您的应用程序被销毁时,访问该文件的权限就会消失。在处理需要长时间许可的文件时,您不应使用DocumentFile.fromSingleUri(context, uri)。因此,我们需要 DocumentFile.fromTreeUri(context, uri)

为此,您必须首先访问 SD 卡 ID 的根目录(例如 6331-6132)。例如:

String path = "6331-6132:Video/Iykwim.mp4";
String sdcardId = path.substring(0, path.indexOf(':') + 1); // => returns '6331-6132:'
Uri uri = Uri.parse("content://com.android.externalstorage.documents/tree/" + Uri.encode(sdcardId));
DocumentFile root = DocumentFile.fromTreeUri(context, uri);

// Now, we already have the root of SD card.
// Next, we will get into => Video => Iykwim.mp4
DocumentFile file = root.findFile("Video").findFile("Iykwim.mp4");

请注意,您不能像这样直接访问文件Uri:

String path = "6331-6132:Video/Iykwim.mp4";
Uri uri = Uri.parse("content://com.android.externalstorage.documents/tree/" + Uri.encode(path));
DocumentFile file = DocumentFile.fromTreeUri(context, uri);

或者像这样:

String folder = "6331-6132:Video";
Uri uri = Uri.parse("content://com.android.externalstorage.documents/tree/" + Uri.encode(folder));
DocumentFile directoryVideo = DocumentFile.fromTreeUri(context, uri);
DocumentFile file = directoryVideo.findFile("Iykwim.mp4");

最后,您可以使用Intent.ACTION_VIEW打开文件:

Intent intent = new Intent(Intent.ACTION_VIEW)
            .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            .setData(documentFile.getUri());
context.startActivity(intent);

最糟糕的情况是当您从一个包含很多文件的文件夹中调用 findFile() 时。这将花费很长时间,并可能导致您的应用程序崩溃。确保您始终从不同的线程调用此方法。

Android 一天比一天糟糕。他们制作了一个存储访问框架 (SAF),使您难以管理 SD 卡上的文件。自 Android Kitkat 以来,几乎不推荐使用 java.io.File。您应该改用 DocumentFile