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

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))
    }
}

问题分析

  1. Retrofit接口手动设置Content-Type冲突
    使用@Multipart注解时,Retrofit会自动生成包含正确boundary的multipart/form-data请求头。手动通过@Header设置该头会覆盖自动生成的内容,导致后端无法正确解析MultipartFile。

  2. 变量作用域错误
    在imagePath非空分支中,重新定义了局部变量val file = File(...),未给外层的var file:File?=null赋值,导致后续fileNameRequestBody使用的file?.name始终为null。

  3. RequestBody的MediaType错误
    单个图片文件的RequestBody应使用对应图片类型(如image/jpeg、image/png),而非multipart/form-data(这是整个请求的类型)。

  4. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 00:01:09