【问题标题】:Android Kotlin: Getting a FileNotFoundException with filename chosen from file picker?Android Kotlin:使用从文件选择器中选择的文件名获取 FileNotFoundException?
【发布时间】:2020-03-26 03:15:34
【问题描述】:

我正在开发一个 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()
        }
    }
}

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

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 file exception kotlin filenotfoundexception


    【解决方案1】:

    您没有收到文件路径,您收到的是Uri。您必须使用基于Uri 的API,例如ContentResolver.openInputStream() 来访问Uri 的内容,因为Android 不会授予您的应用直接File 访问底层文件的权限(也可以从Google Drive 流式传输或下载直接从 Internet 获取,而您的应用没有意识到这种情况):

    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】:

      您无法在转换为字符串的Ù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

      【讨论】:

        【解决方案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”。

        【讨论】:

        • 它是否也适用于文档文件,例如 .pdf / .docx / .xls 等?
        • @NarendraSingh 它会工作,但在上面的代码中我使用了缓存目录。请确保文件大小符合要求。
        • 对于较大的文件,我们可以使用files目录,对吧?
        • @SANAT 我们可以获取文档的实际名称而不是我们的名称(例如此处的“temp”)吗?
        • @AroshiS 尝试从 URI 获取文件名:stackoverflow.com/questions/5568874/…
        【解决方案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;
        }
        

        【讨论】:

          【解决方案5】:

          对于 msf: 文件 uri 格式,来自 Andorid 10。

          您可以查看此解决方案:https://stackoverflow.com/a/68827976/15342371

          此获取路径不复制文件。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-10-05
            相关资源
            最近更新 更多