我正在开发一个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拼图/hard001.txt flg = 0x1}}到活动{com.example.sudokusolver / com.example.sudokusolver.MainActivity}:java.io.FileNotFoundException:/ document / 0000-0000:数独拼图/hard001.txt(无此类文件或目录)


#1 楼

您没有收到文件路径,而是收到Uri。您必须使用基于Uri的API(例如ContentResolver.openInputStream())来访问该Uri上的内容,因为Android并不授予您的应用直接File对基础文件的访问权限(也可以从Google云端硬盘流式传输,也可以直接从互联网上下载而不使用您的应用请注意,这种情况正在发生):

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)


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

#2 楼

如果您在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。”。

评论


它也适用于文档文件,例如.pdf / .docx / .xls等吗?

– Narendra Singh
7月3日10:55

@NarendraSingh可以使用,但是在上面的代码中,我使用了缓存目录。请确保文件大小符合要求。

– SANAT
7月3日15:22

对于较大的文件,我们可以使用files目录,对吗?

– Narendra Singh
7月3日15:43

@SANAT是否可以获取文档的实际名称,而不是为其提供名称(例如此处的“ temp”)?

– AroshiS
7月13日8:04



@AroshiS尝试从URI获取文件名:stackoverflow.com/questions/5568874/…

– SANAT
7月13日晚上11:25

#3 楼

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

使用contentResolver获取Java FileDescriptor打开

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


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

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