Android Kotlin:获取从文件选取器中选择文件名的 FileNotFoundException?

Android Kotlin: Getting a FileNotFoundException with filename chosen from file picker?

提问人:CalicoSkies 提问时间:12/1/2019 更新时间:8/18/2021 访问量:10891

问:

我正在开发一个 Android 应用程序,其中一项功能是让用户选择要打开的文件(我想打开一个纯文本.txt文件)。我以前用 Java 开发过 Android 应用程序,但这次我使用的是 Kotlin,这是我第一次使用 Kotlin。

我目前让应用程序显示一个文件选择器,让用户点击他们想要打开的文件。然后我尝试使用 File 对象打开文件并执行 forEachLine 循环。但出于某种原因,它抛出一个 java.io.FileNotFoundException(没有这样的文件或目录),其中包含从文件选取器中选择的文件。我不确定出了什么问题,如果我必须进行一些转换来转换文件路径?

我的“加载”按钮的代码:

val btn_load: Button = findViewById<Button>(R.id.btn_load_puzzle)
    btn_load.setOnClickListener {
        val intent = Intent()
            .setType("*/*")
            .setAction(Intent.ACTION_GET_CONTENT)

        startActivityForResult(Intent.createChooser(intent, "Select a file"), 111)
    }

我的函数来响应文件选择:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    // Selected a file to load
    if ((requestCode == 111) && (resultCode == RESULT_OK)) {
        val selectedFilename = data?.data //The uri with the location of the file
        if (selectedFilename != null) {
            val filenameURIStr = selectedFilename.toString()
            if (filenameURIStr.endsWith(".txt", true)) {
                val msg = "Chosen file: " + filenameURIStr
                val toast = Toast.makeText(applicationContext, msg, Toast.LENGTH_SHORT)
                toast.show()
                File(selectedFilename.getPath()).forEachLine {
                    val toast = Toast.makeText(applicationContext, it, Toast.LENGTH_SHORT)
                    toast.show()
                }
            }
            else {
                val msg = "The chosen file is not a .txt file!"
                val toast = Toast.makeText(applicationContext, msg, Toast.LENGTH_LONG)
                toast.show()
            }
        }
        else {
            val msg = "Null filename data received!"
            val toast = Toast.makeText(applicationContext, msg, Toast.LENGTH_LONG)
            toast.show()
        }
    }
}

FileNotFound 异常在它创建 File 对象以执行 forEachLine 循环的行上引发:

java.lang.RuntimeException:无法将结果 ResultInfo{who=null, request=111, result=-1, data=Intent { dat=content://com.android.externalstorage.documents/document/0000-0000:Sudoku puzzles/hard001.txt flg=0x1 }} 传送到活动 {com.example.sudokusolver/com.example.sudokusolver.MainActivity}: java.io.FileNotFoundException: /document/0000-0000:Sudoku puzzles/hard001.txt (没有这样的文件或目录)

Android 文件 异常 Kotlin FileNotFoundException

评论


答:

16赞 ianhanniballake 12/1/2019 #1

您没有收到文件路径,而是收到了 .您必须使用基于 ContentResolver.openInputStream() 的 API 来访问内容,因为 Android 不会授予您的应用直接访问底层文件的权限(它也可以从 Google Drive 流式传输或直接从互联网下载,而您的应用不知道这种情况正在发生):UriUriUriFile

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    // Selected a file to load
    if ((requestCode == 111) && (resultCode == RESULT_OK)) {
        val selectedFilename = data?.data //The uri with the location of the file
        if (selectedFilename != null) {
            contentResolver.openInputStream(selectedFilename)?.bufferedReader()?.forEachLine {
                val toast = Toast.makeText(applicationContext, it, Toast.LENGTH_SHORT)
                toast.show()
            }
        } else {
            val msg = "Null filename data received!"
            val toast = Toast.makeText(applicationContext, msg, Toast.LENGTH_LONG)
            toast.show()
        }
    }
}

在这里,我们可以假设我们通过将正确的 mime 类型传递给我们的请求来获取正确格式的内容(因为不要求文本文件以扩展名作为其路径的一部分):.txt

val intent = Intent()
    .setType("text/*")
    .setAction(Intent.ACTION_GET_CONTENT)

startActivityForResult(Intent.createChooser(intent, "Select a file"), 111)

这将自动使任何非文本文件无法被选中。

5赞 Andrew 12/1/2019 #2

您不能在转换为字符串的 Java 文件上打开 Java,URI 的“path”部分与物理文件位置无关。ÙRI

使用 a 获取用于打开文件的 Java。contentResolverFileDescriptor

val parcelFileDescriptor: ParcelFileDescriptor =
            contentResolver.openFileDescriptor(uri, "r")
    val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor

此方法与 Android 10 兼容,其中非应用专用目录的文件路径不可用。

https://developer.android.com/training/data-storage/shared/documents-files

8赞 SANAT 6/2/2020 #3

如果您在 URI 中收到“msf:xxx”,请使用以下解决方案,我在应用程序缓存目录中创建了临时文件,并在完成任务后删除了相同的文件:

if (id != null && id.startsWith("msf:")) {
                    final File file = new File(mContext.getCacheDir(), Constant.TEMP_FILE + Objects.requireNonNull(mContext.getContentResolver().getType(imageUri)).split("/")[1]);
                    try (final InputStream inputStream = mContext.getContentResolver().openInputStream(imageUri); OutputStream output = new FileOutputStream(file)) {
                        final byte[] buffer = new byte[4 * 1024]; // or other buffer size
                        int read;

                        while ((read = inputStream.read(buffer)) != -1) {
                            output.write(buffer, 0, read);
                        }

                        output.flush();
                        return file;
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                    return null;
                }

我已经解决了这个问题,它对 msf 100% 有效。:)

此外,在完成工作后删除临时文件:

private void deleteTempFile() {
        final File[] files = requireContext().getCacheDir().listFiles();
        if (files != null) {
            for (final File file : files) {
                if (file.getName().contains(Constant.TEMP_FILE)) {
                    file.delete();
                }
            }
        }
    }

这里的值TEMP_FILE是“temp”。

评论

0赞 Narendra Singh 7/3/2020
它是否也适用于文档文件,如 .pdf / .docx / .xls 等?
0赞 SANAT 7/3/2020
@NarendraSingh 它会起作用,但在上面的代码中,我使用了缓存目录。请确保文件大小符合它。
0赞 Narendra Singh 7/3/2020
对于较大的文件,我们可以使用 files 目录,对吧?
0赞 AroshiS 7/13/2020
@SANAT 我们可以得到文档的实际名称而不是给它起我们的名字(比如这里的“temp”)吗?
1赞 SANAT 7/13/2020
@AroshiS 尝试从 URI: stackoverflow.com/questions/5568874/...
1赞 miraquee 1/13/2021 #4

根据其 URI 打开位图文件:

private Bitmap getBitmapFromUri(Uri uri) throws IOException {
    ParcelFileDescriptor parcelFileDescriptor =
            getContentResolver().openFileDescriptor(uri, "r");
    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
    parcelFileDescriptor.close();
    return image;
}