使用GligarPicker获取多存储路径图片后,复制至应用级文件夹时FileNotFoundException问题解决
问题分析与修复方案
你遇到的EISDIR错误本质是把应用缓存目录本身当成了要写入的文件,再加上代码里错误地调用了file.mkdir()把目标路径创建成了目录,后续尝试用FileOutputStream打开这个目录时自然会报错。咱们一步步拆解问题,然后给出修复后的完整代码:
核心错误点
- 路径构造错误:你构造的
File对象如果处理不当(比如getFileName返回空),会直接指向externalCacheDir目录,而不是目录下的具体文件; - 多余的
mkdir()调用:file.mkdir()会把目标路径创建为目录,之后createNewFile()其实会失效(因为已经是目录了),最后用FileOutputStream打开目录必然触发EISDIR; - Uri处理不够通用:直接拼接
file://的方式不适用于所有场景(比如图片来自MediaStore时,Uri格式是content://),可能导致后续流获取失败。
修复后的完整代码
// 遍历用户选择的图片列表 imagesList.forEach { imagePath -> try { // 1. 正确构造Uri:如果路径已经以file://开头就直接用,否则补全前缀 val uri = Uri.parse(if (imagePath.startsWith("file://")) imagePath else "file://$imagePath") // 2. 安全获取文件名:避免为空的情况,用时间戳生成临时文件名兜底 val fileName = requireContext().contentResolver.getFileName(uri) ?: "upload_image_${System.currentTimeMillis()}.jpeg" // 3. 构造目标文件:确保是externalCacheDir目录下的具体文件,而非目录本身 val targetFile = File(requireContext().externalCacheDir, fileName) // 4. 用ContentResolver打开输入流(支持所有Uri类型,比直接操作FileDescriptor更通用) requireContext().contentResolver.openInputStream(uri)?.use { inputStream -> // 5. 打开输出流写入文件,use块会自动关闭流,避免资源泄漏 FileOutputStream(targetFile).use { outputStream -> inputStream.copyTo(outputStream, bufferSize = 8192) // 指定缓冲大小提升效率 } } // 这里可以调用Retrofit上传逻辑,比如把targetFile转换成MultipartBody.Part val uploadPart = createMultipartUploadPart(targetFile) // uploadToServer(uploadPart) } catch (e: IOException) { e.printStackTrace() // 这里可以给用户提示复制失败,比如Toast.makeText(requireContext(), "图片复制失败", Toast.LENGTH_SHORT).show() } } // 扩展函数:从Uri中获取文件名(兼容file://和content://类型的Uri) fun ContentResolver.getFileName(uri: Uri): String? { // 先尝试从MediaStore查询文件名(适用于content://类型的Uri) val projection = arrayOf(MediaStore.MediaColumns.DISPLAY_NAME) query(uri, projection, null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { return cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)) } } // 如果查询失败,从Uri路径中提取文件名(适用于file://类型的Uri) return uri.path?.let { File(it).name } } // 辅助函数:将File转换为Retrofit所需的MultipartBody.Part fun createMultipartUploadPart(file: File): MultipartBody.Part { val requestBody = file.asRequestBody("image/jpeg".toMediaTypeOrNull()) return MultipartBody.Part.createFormData("images", file.name, requestBody) }
关键优化点
- 移除
mkdir()调用:FileOutputStream会自动创建不存在的文件(前提是父目录externalCacheDir存在,这个目录是系统为应用自动创建的,无需手动创建); - 用
use块管理流:Kotlin的use函数会在代码块执行完毕后自动关闭流,避免资源泄漏; - 通用的Uri处理:通过
ContentResolver.openInputStream可以兼容file://和content://等多种Uri类型,适配更多图片来源场景; - 文件名兜底逻辑:避免因
getFileName返回空导致路径错误,用时间戳生成唯一文件名; - 指定缓冲大小:
copyTo时指定缓冲大小可以提升文件复制的效率。
额外说明
如果你的需求必须使用FileDescriptor(比如需要更底层的操作),可以调整输入流的获取方式,但核心还是要确保目标路径是具体文件而非目录:
val parcelFileDescriptor = requireContext().contentResolver.openFileDescriptor(uri, "r") ?: return val fileDescriptor = parcelFileDescriptor.fileDescriptor FileInputStream(fileDescriptor).use { inputStream -> FileOutputStream(targetFile).use { outputStream -> inputStream.copyTo(outputStream) } } parcelFileDescriptor.close() // 注意手动关闭ParcelFileDescriptor,或者用use块包裹
内容的提问来源于stack exchange,提问作者sri
相关产品推荐
相关产品推荐

