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对象没有在方法里定义,正常编译阶段就会报错。
修复方案
按以下步骤修改代码即可:
- 修正接口定义:删除手动添加的全局Content-Type头、删除多余的Content-Type参数,文件字段改用
MultipartBody.Part类型接收,普通字段传RequestBody类型。 - 修正文件RequestBody创建逻辑:给ZIP文件设置正确的
application/zipMediaType,通过MultipartBody.Part.createFormData创建文件部件,指定表单字段名和文件名。 - 普通字符串参数转为
text/plain类型的RequestBody传输。 - 补全错误处理逻辑:在onFailure里打印堆栈,onResponse里先判断请求是否成功,失败时读取errorBody的原始内容排查问题。
- 修正笔误的变量名,如果用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
相关产品推荐
相关产品推荐

