You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 12:27:45