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

Kotlin使用Retrofit上传ZIP文件至服务器无报错返回null问题求解

问题根因

返回响应为NULL是以下几个问题共同导致的:

  • 手动覆写了multipart/form-data请求头:@Multipart注解会自动生成带随机boundary参数的Content-Type头,你在接口上硬写@Headers("Content-Type: multipart/form-data")、在方法参数里重复传Content-Type头,会把自动生成的合法头覆盖,服务器无法解析请求体,自然返回不符合预期的内容。
  • 文件部件写法不符合新版Retrofit规范:直接用@Part("Zip")接收RequestBody的写法已废弃,这种写法不会给文件部分生成带filename字段的Content-Disposition头,服务器识别不到上传的ZIP文件。同时你给ZIP文件设置的MediaType错误,把单个文件的类型写成了整个请求的multipart/form-data,正确的ZIP文件MediaType应为application/zip。
  • 普通表单字段写法错误:description、flag这类普通字符串参数直接传字符串值会被Retrofit当成多部件处理,不会按普通表单字段传输,服务器拿不到对应参数。
  • 错误逻辑缺失:onFailure回调里没有打印错误堆栈,onResponse里没有判断response.isSuccessful、没有读取errorBody内容,哪怕请求返回4xx/5xx错误、Gson解析失败,你也看不到具体报错,只会拿到null的response.body()。
  • 代码存在变量名笔误:你发起请求时调用的startZipeFileUpload对象没有在方法里定义,正常编译阶段就会报错。
修复方案

按以下步骤修改代码即可:

  1. 修正接口定义:删除手动添加的全局Content-Type头、删除多余的Content-Type参数,文件字段改用MultipartBody.Part类型接收,普通字段传RequestBody类型。
  2. 修正文件RequestBody创建逻辑:给ZIP文件设置正确的application/zipMediaType,通过MultipartBody.Part.createFormData创建文件部件,指定表单字段名和文件名。
  3. 普通字符串参数转为text/plain类型的RequestBody传输。
  4. 补全错误处理逻辑:在onFailure里打印堆栈,onResponse里先判断请求是否成功,失败时读取errorBody的原始内容排查问题。
  5. 修正笔误的变量名,如果用HTTP明文请求记得在AndroidManifest的application标签下加android:usesCleartextTraffic="true"适配系统限制。

修正后的接口代码:

interface SendZipeFile{
    // 删掉硬写的Content-Type头,@Multipart会自动生成带正确boundary的头
    @Multipart
    @POST("updateModelsAndParameters/")
    fun zipeFileUpload(
        @Part("description") description: RequestBody?,
        @Part zipFile: MultipartBody.Part?,
        @Part("flag") flag: RequestBody?,
    ): Call<UploadZipFile>
}

修正后的上传请求代码:

fun sendZipeFile() {
    val retrofitClient = NetworkUtils.getRetrofitInstance("http://url.com")
    val sendZipeFileEndPoint = retrofitClient.create(SendZipeFile::class.java)

    val file = File("/storage/emulated/0/Download/file.zip")
    // 单个zip文件的MediaType设为application/zip,不要写multipart/form-data
    val zipMediaType = "application/zip".toMediaType()
    val fileRequestBody = file.asRequestBody(zipMediaType)
    // 创建文件部件,指定表单字段名是Zip,带上原文件名
    val zipPart = MultipartBody.Part.createFormData("Zip", file.name, fileRequestBody)
    // 普通字符串参数转成text/plain类型的RequestBody
    val descBody = "Atualização".toRequestBody("text/plain".toMediaType())
    val flagBody = "0.0.2".toRequestBody("text/plain".toMediaType())

    val call = sendZipeFileEndPoint.zipeFileUpload(descBody, zipPart, flagBody)

    call.enqueue(object : Callback<UploadZipFile> {
        override fun onFailure(call: Call<UploadZipFile>, t: Throwable) {
            // 打印完整错误堆栈
            t.printStackTrace()
        }

        override fun onResponse(call: Call<UploadZipFile>, response: Response<UploadZipFile>) {
            if (response.isSuccessful) {
                val result = response.body()
                // 处理正常返回结果
            } else {
                // 读取服务器返回的原始错误信息排查问题
                val errorMsg = response.errorBody()?.string()
                Log.e("UploadError", "错误码:${response.code()}, 错误信息:$errorMsg")
            }
        }
    })
}

修复后如果还是返回null,先看错误分支打印的错误码和服务器原始返回,大概率是服务器要求的字段名、校验规则和你传的不匹配,根据返回内容调整参数即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 02:06:31