Android Kotlin Retrofit POST多部分请求报400错误排查求助
我参考多篇教程在Android Kotlin中实现Retrofit的POST Multipart请求,目标调用OpenAI的v1/images/variations接口,请求体需要包含n、size参数以及图片文件,请求头要带Bearer Token。已经写好API接口、Service Builder和调用代码,修改过n和size的参数类型后还是返回Http 400 Bad Request,需要帮忙排查问题。
API接口代码
@Headers("Content-Type: multipart/form-data") @POST("v1/images/variations") @Multipart suspend fun requestVariation(@Header("Authorization") token: String, @Part("n") n: RequestBody, @Part("size") size: RequestBody, @Part image: MultipartBody.Part) :Call<ResponseClass>
Service Builder代码
object VariationBuilder { private val client = OkHttpClient.Builder().build() private val retrofit = Retrofit.Builder() .baseUrl("https://api.openai.com/") .addConverterFactory(GsonConverterFactory.create()) .client(client) .build() fun<T> buildService (service :Class<T>): T{ return retrofit.create(service) } }
MainActivity调用代码
val linearLayout: LinearLayout = findViewById(R.id.img_linear_layout) val numberSpinner: Spinner = findViewById(R.id.spinner) val tokenText: EditText = findViewById(R.id.token_box) linearLayout.removeAllViews() val newID = 1 view.id = newID linearLayout.addView(view) val cantSelec = numberSpinner.selectedItem.toString().toInt() - 1 val retrofit = VariationBuilder.buildService(RestApiInterface::class.java) val dir = File(externalCacheDir,File.separator + "Variations") dir.mkdirs() val fileName = String.format("%d.jpg", System.currentTimeMillis()) val file: File = File(dir,File.separator + fileName) file.createNewFile() //将Bitmap转为字节数组 val bitmap: Bitmap? = createBitmapFromView(this@MainActivity,view) val bos = ByteArrayOutputStream() bitmap?.compress(Bitmap.CompressFormat.PNG, 0, bos) // 也可以保存为JPEG格式 val bitmapdata = bos.toByteArray() //写入文件 val fos = FileOutputStream(file) fos.write(bitmapdata) fos.flush() fos.close() Toast.makeText(applicationContext, "请稍等,加载中...", Toast.LENGTH_LONG).show() retrofit.requestVariation( "Bearer " + tokenText.text.toString(), RequestBody.create(MultipartBody.FORM,cantSelec.toString()), RequestBody.create(MultipartBody.FORM,"512x512"), image = MultipartBody.Part .createFormData( "image", fileName, RequestBody.create(MultipartBody.FORM, file) ) )
问题排查及修复方案
1. 移除手动添加的Content-Type头
使用@Multipart注解时,Retrofit会自动生成包含boundary参数的Content-Type: multipart/form-data头,手动添加的@Headers("Content-Type: multipart/form-data")会覆盖自动生成的头,缺失boundary导致服务器无法解析请求体。直接删除该注解:
@POST("v1/images/variations") @Multipart suspend fun requestVariation(@Header("Authorization") token: String, @Part("n") n: RequestBody, @Part("size") size: RequestBody, @Part image: MultipartBody.Part) :Response<ResponseClass>
2. 修正suspend函数返回值
suspend函数不需要返回Call<ResponseClass>,直接返回Response<ResponseClass>或ResponseClass即可,否则会导致请求执行逻辑异常:
suspend fun requestVariation(@Header("Authorization") token: String, @Part("n") n: RequestBody, @Part("size") size: RequestBody, @Part image: MultipartBody.Part) :Response<ResponseClass>
3. 确保在协程中调用suspend函数
suspend函数必须在协程作用域内执行,否则会抛出异常,导致请求未正常发送。修改调用代码,用lifecycleScope包裹:
lifecycleScope.launch { try { val response = retrofit.requestVariation( "Bearer " + tokenText.text.toString(), RequestBody.create(MultipartBody.FORM,cantSelec.toString()), RequestBody.create(MultipartBody.FORM,"512x512"), image = MultipartBody.Part.createFormData( "image", fileName, RequestBody.create(MediaType.parse("image/png"), file) ) ) if (!response.isSuccessful) { // 打印错误详情,方便定位问题 Log.e("OpenAIErr", response.errorBody()?.string() ?: "未知错误") } } catch (e: Exception) { e.printStackTrace() } }
4. 修正图片文件的MediaType
你将Bitmap压缩为PNG格式,但文件名是.jpg,且用MultipartBody.FORM作为文件的MediaType,这会导致服务器识别文件类型错误。需指定正确的MediaType:
val imageRequestBody = RequestBody.create(MediaType.parse("image/png"), file) val imagePart = MultipartBody.Part.createFormData("image", fileName, imageRequestBody)
5. 修正n参数的取值范围
OpenAI的n参数允许范围是1-10,你的代码中cantSelec = numberSpinner.selectedItem.toString().toInt() - 1会导致取值为0(当Spinner选1时),不符合接口要求。去掉减1操作:
val cantSelec = numberSpinner.selectedItem.toString().toInt()
6. 添加日志拦截器排查请求细节
添加OkHttp日志拦截器,查看实际发送的请求头、请求体是否符合接口要求:
// 修改VariationBuilder中的client配置 private val client = OkHttpClient.Builder() .addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) .build()
同时添加依赖:
implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
内容的提问来源于stack exchange,提问作者eLoco oMojogm

