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

Kotlin使用Retrofit Multipart上传图片到服务器异常解决方案

Kotlin 基于 Retrofit 实现 Multipart 图片上传正确实现

参数为null、请求格式异常问题基本集中在依赖配置、接口定义、Part构造三个环节,按以下步骤配置即可正常运行:

1. 基础依赖配置

先确保引入正确版本的依赖,不要混用不同大版本的Retrofit/OkHttp:

// app模块build.gradle.kts 依赖示例
dependencies {
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
}

2. 上传接口定义

注意:所有RequestBody、MultipartBody.Part 必须使用com.squareup.okhttp3包下的类,不要引入其他同名类。

interface UploadApi {
    // 必须给方法加@Multipart注解,不能用@Body、@Field传Multipart参数
    @Multipart
    @POST("your/upload/api/path") // 替换为自身业务的上传接口路径
    suspend fun uploadImage(
        // 同传的普通文本参数,用@Part标注参数名,传入RequestBody类型
        @Part("userId") userId: RequestBody,
        // 图片参数直接传MultipartBody.Part,不要额外给@Part加key值
        @Part imagePart: MultipartBody.Part
    ): Response<UploadResultBean> // 替换为自身业务的返回实体类
}

接口定义高频错误写法

  • 给MultipartBody.Part类型的参数额外指定@Part的key值,会导致表单key重复,服务端无法识别
  • 直接传File、Uri、ByteArray类型作为上传参数,未转换为RequestBody/MultipartBody.Part,会被序列化器处理为null
  • 手动在接口/拦截器中添加固定的Content-Type: multipart/form-data请求头,会覆盖Retrofit自动生成的带boundary分隔符的合法头,直接导致请求格式解析失败

3. 构造上传参数的正确方法

封装通用转换方法,覆盖本地文件、系统Uri两种常见取图场景:

import android.content.Context
import android.net.Uri
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File

// 普通文本参数转RequestBody
fun String.toPlainTextRequestBody(): RequestBody {
    return this.toRequestBody("text/plain".toMediaTypeOrNull())
}

// 本地图片文件转MultipartPart,partKey需要和服务端要求的参数名完全一致
fun File.toImageMultipartPart(partKey: String = "image"): MultipartBody.Part {
    val imageRequestBody = this.asRequestBody("image/*".toMediaTypeOrNull())
    return MultipartBody.Part.createFormData(partKey, this.name, imageRequestBody)
}

// Android系统Uri转MultipartPart(适配分区存储场景)
fun Uri.toImageMultipartPart(context: Context, partKey: String = "image"): MultipartBody.Part? {
    val contentResolver = context.contentResolver
    val inputStream = contentResolver.openInputStream(this) ?: return null
    val fileBytes = inputStream.readBytes()
    inputStream.close()
    // 读取原始文件名
    val fileName = contentResolver.query(this, null, null, null, null)?.use { cursor ->
        val nameColumnIndex = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
        cursor.moveToFirst()
        cursor.getString(nameColumnIndex)
    } ?: "upload_image_${System.currentTimeMillis()}.jpg"
    val imageRequestBody = fileBytes.toRequestBody("image/*".toMediaTypeOrNull())
    return MultipartBody.Part.createFormData(partKey, fileName, imageRequestBody)
}

4. 初始化与请求发起示例

// 初始化OkHttp,可添加日志拦截器排查请求参数问题
val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY
    })
    .build()

// 初始化Retrofit,baseUrl必须以/结尾
val retrofit = Retrofit.Builder()
    .baseUrl("https://your.server.base.url/") // 替换为自身服务端域名
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val uploadApi = retrofit.create(UploadApi::class.java)

// 协程作用域内发起请求,注意声明网络权限、切换IO线程
lifecycleScope.launch(Dispatchers.IO) {
    runCatching {
        val userIdBody = "10086".toPlainTextRequestBody()
        // 本地文件场景
        val localImage = File("/storage/emulated/0/Pictures/demo.jpg")
        val imagePart = localImage.toImageMultipartPart()
        // 相册选图Uri场景替换为:val imagePart = selectedUri.toImageMultipartPart(context) ?: return@launch

        uploadApi.uploadImage(userIdBody, imagePart)
    }.onSuccess { response ->
        if (response.isSuccessful) {
            // 上传成功逻辑
        } else {
            // 上传失败,读取错误信息
            val errorInfo = response.errorBody()?.string()
        }
    }.onFailure {
        it.printStackTrace()
    }
}

常见问题排查

  • 图片参数为null:确认MultipartPart构造时传入的partKey和服务端要求完全一致(大小写敏感),确认没有直接传File/Uri/ByteArray给接口
  • 请求格式异常:检查是否手动添加了固定的multipart/form-data请求头,检查自定义拦截器是否修改了请求体内容
  • 服务端提示文件损坏:检查读取文件流时是否提前关闭流、字节数组读取长度是否完整

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 21:09:17