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

如何使用Retrofit发送文件?Retrofit2传图遇ENOENT错误求助

Retrofit2上传图片出现ENOENT错误的排查与修复

错误open failed: ENOENT (No such file or directory)的核心原因就是指定路径的文件不存在,先从路径问题入手排查,再修正代码里的细节问题。

一、优先排查文件路径问题

  • 先打印userAvatar的具体值,确认路径格式是否正确:是绝对路径还是相对路径?如果是从相册/相机获取的Uri转来的路径,Android 10+的Scoped Storage机制下,直接用File访问私有目录外的文件会失败。
  • 加个文件存在性判断,提前定位问题:
    val file = File(userAvatar)
    if (!file.exists()) {
        Log.e("FILE_ERROR", "文件不存在: $userAvatar")
        message.value = "头像文件找不到"
        return@launch
    }
    
  • 检查权限:如果是访问外部存储的图片,Android 13+需要申请READ_MEDIA_IMAGES权限,低于13版本需要READ_EXTERNAL_STORAGE权限,没权限也会导致文件无法访问。

二、代码本身的问题点

1. Retrofit接口的冗余配置

你当前的接口注解有错误:

@Headers(
"accept: */*",
"Content-Type: multipart/form-data"
)
@Multipart
@POST("avatar")
suspend fun updateAvatar(@Header("Authorization") auth: String, @Part("file") userImage: RequestBody): Response<JsonObject>
  • @Multipart注解会自动生成带boundary的Content-Type: multipart/form-data头,手动添加的Content-Type会覆盖这个自动配置,导致服务器无法解析多部分请求,必须删掉这个Header。
  • 上传文件更规范的写法是用MultipartBody.Part,可以带上文件名,避免服务器无法识别文件类型:
    @Multipart
    @POST("avatar")
    suspend fun updateAvatar(
        @Header("Authorization") auth: String,
        @Part file: MultipartBody.Part
    ): Response<JsonObject>
    

2. 上传逻辑的细节优化

  • 你现在是先更新用户资料,成功后再传头像,这个逻辑如果符合业务需求没问题,但要注意如果资料更新失败,头像就不会上传,是否是预期行为?
  • userToken.replace("\"", "")这个操作要确认token本身是否真的带引号,避免误操作导致token失效。

三、修复后的完整代码示例

修正后的Retrofit接口

@Multipart
@POST("avatar")
suspend fun updateAvatar(
    @Header("Authorization") auth: String,
    @Part file: MultipartBody.Part
): Response<JsonObject>

修正后的ViewModel逻辑

fun updateProfile(
    firstName: String,
    patronymic: String,
    lastName: String,
    birthday: String,
    gender: String,
    userAvatar: String,
    userToken: String
) {
    // 重置状态变量
    message.value = null
    sendEmailResponseCode.value = null
    checkEmailResponseCode.value = null
    createCardResponseCode.value = null

    viewModelScope.launch {
        val apiService = ApiService.getInstance()
        try {
            val profileResponse = apiService.updateProfile(
                "Bearer ${userToken.replace("\"", "")}",
                mapOf(
                    "firstname" to firstName,
                    "lastname" to lastName,
                    "patronymic" to patronymic,
                    "birthday" to birthday,
                    "gender" to gender
                )
            )

            // 先校验文件是否存在
            val avatarFile = File(userAvatar)
            if (!avatarFile.exists()) {
                message.value = "头像文件不存在: $userAvatar"
                createCardResponseCode.value = profileResponse.code()
                return@launch
            }

            if (profileResponse.isSuccessful) {
                // 构建Multipart文件部分
                val requestBody = avatarFile.asRequestBody("image/*".toMediaType())
                val avatarPart = MultipartBody.Part.createFormData("file", avatarFile.name, requestBody)
                
                val avatarResponse = apiService.updateAvatar("Bearer ${userToken.replace("\"", "")}", avatarPart)
                Log.d("LOG", "头像上传结果: $avatarResponse")
            } else if (profileResponse.code() == 403) {
                message.value = "无权限,请重新登录"
            } else {
                message.value = "资料更新失败,错误码: ${profileResponse.code()}"
            }

            createCardResponseCode.value = profileResponse.code()
        } catch (e: Exception) {
            Log.e("ERROR", "更新流程出错", e)
            message.value = "更新失败: ${e.message}"
        }
    }
}

四、Android平台额外注意事项

如果userAvatar是Content Uri(比如相册返回的content://开头的路径),不能直接用File访问,需要通过ContentResolver读取流来构建RequestBody:

val contentResolver = context.contentResolver
val inputStream = contentResolver.openInputStream(Uri.parse(userAvatar)) ?: throw IOException("无法打开文件流")
val requestBody = RequestBody.create("image/*".toMediaType(), inputStream.readBytes())
val avatarPart = MultipartBody.Part.createFormData("file", "avatar.jpg", requestBody)

内容的提问来源于stack exchange,提问作者TriangleN

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 15:55:02