Android端图片上传报NullPointerException求助(Postman可正常上传)
问题排查:Android图片上传报NullPointerException(MultipartFile为null)
Postman可正常上传图片,但Android应用上传时抛出以下错误:
java.lang.NullPointerException: Cannot invoke "org.springframework.web.multipart.MultipartFile.getOriginalFilename()" because "file" is null
原相关代码
UploadService接口
interface UploadService { @Multipart @POST("/uploadFile") suspend fun uploadFile( @Header("Content-Type") type: String, @Part file: MultipartBody.Part, @Part("fileName") fileName: RequestBody ): Call<UploadFileResponse> }
图片上传方法
suspend fun uploadImage(context: Context, uri: Uri, imagePath: String?): Result<UploadFileResponse> { try { val interceptor = HttpLoggingInterceptor() interceptor.level = HttpLoggingInterceptor.Level.BODY val client: OkHttpClient = OkHttpClient.Builder() .addInterceptor(interceptor) .build() val gson = GsonBuilder().setLenient().create() val service = Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(gson)) .baseUrl("http://10.0.2.2:9000/") .client(client) .build() .create(UploadService::class.java) Console.debug(javaClass.simpleName,"ClassName:ShopOrderProductRepository FunctionName:uploadImage imagePath:${imagePath} file11:file11") var file: File? = null var imagePart: MultipartBody.Part? = null var reqFile: RequestBody? = null if (!imagePath.isNullOrEmpty()) { val file1 = Uri.fromFile( File( context.cacheDir, imagePath//getType(imagePath.path)// ) ).toFile() val file = File(context.cacheDir, "image12.jpg") file.createNewFile() file.outputStream().use { context.assets.open("image12.jpg") } // file=File(imagePath?:"") //reqFile = RequestBody.create("image/*".toMediaTypeOrNull(), file) reqFile = RequestBody.create("multipart/form-data".toMediaTypeOrNull(), file) imagePart = MultipartBody.Part .createFormData( name = "profile_image", filename = file?.name, body = reqFile!!//file.asRequestBody() ) } else { val stream = context.contentResolver.openInputStream(uri) //var requestFile = RequestBody.create("multipart/form-data".toMediaTypeOrNull(), stream!!.readBytes()) var bytes = stream!!.readBytes() Console.debug(javaClass.simpleName,"ClassName:ShopOrderProductRepository FunctionName:uploadImage imagePart:${imagePart} bytes:${bytes.size} bytes:${bytes.toString()}") // reqFile = RequestBody.create("image/*".toMediaTypeOrNull(), bytes) reqFile = RequestBody.create("multipart/form-data".toMediaTypeOrNull(), bytes) imagePart = MultipartBody.Part .createFormData( name = "profile_image", filename = "profile.png",//file?.name, body = reqFile ) } val fileNameRequestBody = RequestBody.create("text/plain".toMediaTypeOrNull(), file?.name ?: "") try { var response = service?.uploadFile("multipart/form-data;boundary=" + System.currentTimeMillis(), imagePart, fileNameRequestBody)?.awaitResponse() if (response?.isSuccessful ?: false) { return Result.Success(response?.body()!!) } else { return Result.Error(Exception(response?.message())) } } catch (e: Exception) { ExceptionHandler.handle(e) return Result.Error(Exception("Error busiCode:", e)) } } catch (e: Exception) { ExceptionHandler.handle(e) return Result.Error(IOException("Error busiCode:registershops", e)) } }
问题分析
Retrofit接口手动设置Content-Type冲突
使用@Multipart注解时,Retrofit会自动生成包含正确boundary的multipart/form-data请求头。手动通过@Header设置该头会覆盖自动生成的内容,导致后端无法正确解析MultipartFile。变量作用域错误
在imagePath非空分支中,重新定义了局部变量val file = File(...),未给外层的var file:File?=null赋值,导致后续fileNameRequestBody使用的file?.name始终为null。RequestBody的MediaType错误
单个图片文件的RequestBody应使用对应图片类型(如image/jpeg、image/png),而非multipart/form-data(这是整个请求的类型)。assets文件读取未写入输出流
读取assets中的image12.jpg时,仅打开了输入流但未将内容写入到目标文件的输出流中,导致上传的文件为空。
修复后的代码
UploadService接口(移除手动Content-Type设置)
interface UploadService { @Multipart @POST("/uploadFile") suspend fun uploadFile( @Part file: MultipartBody.Part, @Part("fileName") fileName: RequestBody ): Call<UploadFileResponse> }
图片上传方法(修复变量、MediaType、流写入等问题)
suspend fun uploadImage(context: Context, uri: Uri, imagePath: String?): Result<UploadFileResponse> { try { val interceptor = HttpLoggingInterceptor() interceptor.level = HttpLoggingInterceptor.Level.BODY val client: OkHttpClient = OkHttpClient.Builder() .addInterceptor(interceptor) .build() val gson = GsonBuilder().setLenient().create() val service = Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(gson)) .baseUrl("http://10.0.2.2:9000/") .client(client) .build() .create(UploadService::class.java) Console.debug(javaClass.simpleName,"ClassName:ShopOrderProductRepository FunctionName:uploadImage imagePath:${imagePath}") var file: File? = null var imagePart: MultipartBody.Part? = null var reqFile: RequestBody? = null var fileName = "" if (!imagePath.isNullOrEmpty()) { // 修复变量作用域:给外层file赋值 file = File(context.cacheDir, "image12.jpg") file.createNewFile() // 修复流写入:将assets文件内容写入目标文件 context.assets.open("image12.jpg").use { inputStream -> file.outputStream().use { outputStream -> inputStream.copyTo(outputStream) } } fileName = file.name // 修复MediaType:使用图片类型 reqFile = RequestBody.create("image/jpeg".toMediaTypeOrNull(), file) imagePart = MultipartBody.Part.createFormData( name = "profile_image", filename = fileName, body = reqFile ) } else { val stream = context.contentResolver.openInputStream(uri) ?: throw IOException("无法打开图片流") val bytes = stream.readBytes() Console.debug(javaClass.simpleName,"ClassName:ShopOrderProductRepository FunctionName:uploadImage bytes:${bytes.size}") fileName = "profile.png" // 修复MediaType:使用图片类型 reqFile = RequestBody.create("image/png".toMediaTypeOrNull(), bytes) imagePart = MultipartBody.Part.createFormData( name = "profile_image", filename = fileName, body = reqFile ) } val fileNameRequestBody = RequestBody.create("text/plain".toMediaTypeOrNull(), fileName) return try { val response = service.uploadFile(imagePart, fileNameRequestBody).awaitResponse() if (response.isSuccessful) { Result.Success(response.body()!!) } else { Result.Error(Exception(response.message())) } } catch (e: Exception) { ExceptionHandler.handle(e) Result.Error(Exception("上传失败", e)) } } catch (e: Exception) { ExceptionHandler.handle(e) Result.Error(IOException("上传异常", e)) } }
额外说明
- 若后端期望的文件参数名不是
profile_image,需对应修改createFormData中的name参数,确保和后端@RequestParam("xxx") MultipartFile file的参数名一致。 - 图片MediaType建议根据实际文件类型动态获取(如通过Uri或文件后缀判断),避免硬编码。
内容的提问来源于stack exchange,提问作者Ankush
相关产品推荐
相关产品推荐

