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



我正在开发一个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: Failure Delivery result ResultInfo{who=null, request=111, result=-1, data=Intent { dat=content://com.android.externalstorage.documents/document/0000-0000:Sudoku puzzles/hard001.txt flg=0x1 }} to activity {com.example.sudokusolver/com.example.sudokusolver.MainActivity}: java.io.FileNotFoundException:/document/0000-0000:Sudoku puzzles/hard001.txt (没有这样的文件或目录(

您没有收到文件路径,您收到了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)

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

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

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

使用contentResolver获取用于打开文件的 JavaFileDescriptor

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

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

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

打开给定 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;
}

最新更新