将android.net.Uri转为java.io.File时出错:Retrofit2图片上传异常
解决Retrofit2上传content:// Uri图片的FileNotFoundException问题
content:// 类型的Uri是Android内容提供者(Content Provider)的统一资源标识符,它并不直接对应文件系统中的真实路径,而是指向媒体库或其他应用共享的内容。直接用Uri.path或toFile()转换为File对象必然失败,因为:
Uri.path返回的是内容提供者内部的路径片段,不是文件系统的绝对路径toFile()仅支持file://scheme的Uri,对content Uri不适用
以下是两种可行的解决方案:
方案1:将content Uri内容复制到应用私有临时文件
把content指向的内容复制到应用的缓存目录(cacheDir),这里应用拥有完全访问权限,之后用这个临时文件生成Retrofit请求体。
代码实现
import android.content.Context import android.net.Uri import java.io.File import java.io.FileOutputStream import java.io.IOException fun createTempFileFromUri(context: Context, imageUri: Uri): File? { return try { // 获取content输入流 val inputStream = context.contentResolver.openInputStream(imageUri) ?: return null // 在缓存目录创建临时文件 val tempFile = File(context.cacheDir, "temp_upload_${System.currentTimeMillis()}.jpg") // 复制流数据到临时文件 FileOutputStream(tempFile).use { outputStream -> inputStream.copyTo(outputStream) } inputStream.close() tempFile } catch (e: IOException) { e.printStackTrace() null } } // 上传逻辑示例 fun uploadImage(context: Context, imageUri: Uri, apiService: ApiService) { val tempFile = createTempFileFromUri(context, imageUri) ?: return val requestBody = tempFile.asRequestBody("image/jpeg".toMediaTypeOrNull()) val imagePart = MultipartBody.Part.createFormData( name = "image", // 对应接口的表单字段名 filename = tempFile.name, body = requestBody ) // 协程中执行上传 CoroutineScope(Dispatchers.IO).launch { try { val response = apiService.uploadImage(imagePart) // 处理上传成功逻辑 } catch (e: Exception) { // 处理上传失败逻辑 } finally { // 无论成败都删除临时文件,避免占用空间 tempFile.delete() } } }
方案2:直接基于输入流创建RequestBody(无需临时文件)
Retrofit的RequestBody支持自定义写入逻辑,我们可以直接从content Uri的输入流读取数据并写入请求体,跳过File对象的创建,更高效。
代码实现
import android.content.Context import android.net.Uri import okhttp3.BufferedSink import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody import java.io.IOException fun createRequestBodyFromUri(context: Context, imageUri: Uri): RequestBody? { val mimeType = context.contentResolver.getType(imageUri) ?: "image/jpeg" return object : RequestBody() { override fun contentType() = mimeType.toMediaTypeOrNull() override fun writeTo(sink: BufferedSink) { context.contentResolver.openInputStream(imageUri)?.use { inputStream -> sink.writeAll(inputStream.source()) } ?: throw IOException("Failed to open input stream for $imageUri") } } } // 上传逻辑示例 fun uploadImage(context: Context, imageUri: Uri, apiService: ApiService) { val requestBody = createRequestBodyFromUri(context, imageUri) ?: return val imagePart = MultipartBody.Part.createFormData( name = "image", filename = "upload.jpg", // 可自定义文件名,或从Uri解析 body = requestBody ) CoroutineScope(Dispatchers.IO).launch { try { val response = apiService.uploadImage(imagePart) // 处理上传成功逻辑 } catch (e: Exception) { // 处理上传失败逻辑 } } }
注意事项
- 权限处理:如果是通过系统文件选择器(如
ACTION_GET_CONTENT)获取的Uri,系统已授予临时访问权限,无需额外申请存储权限;若直接访问媒体库,Android 13+需申请READ_MEDIA_IMAGES权限,Android 10-12可通过MediaStore访问或申请READ_EXTERNAL_STORAGE。 - MIME类型:尽量用
contentResolver.getType(imageUri)获取真实MIME类型,避免硬编码导致服务器无法识别文件类型。 - 异常处理:务必捕获IO异常,处理输入流获取失败、文件写入失败等场景。
内容的提问来源于stack exchange,提问作者shyam
相关产品推荐
相关产品推荐

