Android SDK33下如何通过Retrofit上传Bitmap图片?
解决方案
针对你遇到的Retrofit上传图片问题,以下是几种可行的处理方案,按优先级排序:
方案1:改用Multipart上传(推荐,更高效)
如果后端支持调整接口,Multipart是文件上传的标准方式,能避免Base64的体积膨胀,同时完全避开Scoped Storage限制:
修改Retrofit API接口
@Multipart @POST("/image") suspend fun uploadImage( @Part image: MultipartBody.Part ): Response<UploadImageResponse>
从Uri生成MultipartBody.Part
直接通过ContentResolver读取Uri对应的文件流,无需创建临时文件:
// 将Uri转为Multipart请求体 fun uriToMultipartPart(contentResolver: ContentResolver, uri: Uri): MultipartBody.Part? { return try { // 获取文件MIME类型,默认用image/jpeg val mimeType = contentResolver.getType(uri) ?: "image/jpeg" // 获取文件名(可选,后端不需要可省略) val fileName = contentResolver.query(uri, arrayOf(MediaStore.Images.Media.DISPLAY_NAME), null, null, null)?.use { cursor -> if (cursor.moveToFirst()) cursor.getString(0) else "uploaded_image.jpg" } ?: "uploaded_image.jpg" val requestBody = object : RequestBody() { override fun contentType(): MediaType? = MediaType.parse(mimeType) override fun writeTo(sink: BufferedSink) { contentResolver.openInputStream(uri)?.use { inputStream -> sink.writeAll(inputStream.source()) } } } MultipartBody.Part.createFormData("image", fileName, requestBody) } catch (e: IOException) { e.printStackTrace() null } }
调用上传接口
在获取到imageUri后,直接生成Part并上传:
imageUri?.let { uri -> val imagePart = uriToMultipartPart(contentResolver, uri) imagePart?.let { viewModelScope.launch { val response = apiService.uploadImage(it) // 处理上传结果 } } }
方案2:压缩Bitmap后转Base64(不修改API的前提下)
如果无法调整后端接口,需要通过Base64上传,核心是压缩Bitmap的尺寸和质量,大幅减少Base64字符串长度:
压缩并转换的工具方法
// 压缩Bitmap并转为Base64 fun compressBitmapToBase64(bitmap: Bitmap, maxSize: Int = 1024, quality: Int = 50): String { // 缩放Bitmap到指定最大尺寸(宽高取较小值) val scaleFactor = min(maxSize.toFloat() / bitmap.width, maxSize.toFloat() / bitmap.height) val scaledBitmap = if (scaleFactor < 1) { Bitmap.createScaledBitmap(bitmap, (bitmap.width * scaleFactor).toInt(), (bitmap.height * scaleFactor).toInt(), true) } else { bitmap } // 压缩为JPEG字节流 val outputStream = ByteArrayOutputStream() scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream) val byteArray = outputStream.toByteArray() // 回收缩放后的Bitmap释放内存 if (scaledBitmap != bitmap) scaledBitmap.recycle() // 转为Base64字符串 return Base64.encodeToString(byteArray, Base64.DEFAULT) }
调用上传接口
在获取到bitmap后,压缩并生成Base64,再传入请求体:
bitmap?.let { val compressedBase64 = compressBitmapToBase64(it) val uploadRequest = UploadImageRequest(compressedBase64) viewModelScope.launch { val response = apiService.uploadImage(uploadRequest) // 处理上传结果 } }
方案3:直接读取Uri流压缩转Base64(优化内存占用)
如果不想先把Uri转为Bitmap(避免大图片占用过多内存),可以直接读取Uri的输入流进行压缩转换:
// 从Uri直接读取流并转为压缩后的Base64 fun uriToCompressedBase64(contentResolver: ContentResolver, uri: Uri, maxSize: Int = 1024, quality: Int = 50): String? { return try { contentResolver.openInputStream(uri)?.use { inputStream -> // 先获取图片原始尺寸,不加载完整Bitmap val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } BitmapFactory.decodeStream(inputStream, null, options) inputStream.reset() // 重置流指针 // 计算缩放比例 val scaleFactor = calculateScaleFactor(options.outWidth, options.outHeight, maxSize) // 加载缩放后的Bitmap val scaledOptions = BitmapFactory.Options().apply { inSampleSize = scaleFactor } val bitmap = BitmapFactory.decodeStream(inputStream, null, scaledOptions) bitmap?.let { val outputStream = ByteArrayOutputStream() it.compress(Bitmap.CompressFormat.JPEG, quality, outputStream) val byteArray = outputStream.toByteArray() bitmap.recycle() Base64.encodeToString(byteArray, Base64.DEFAULT) } } } catch (e: IOException) { e.printStackTrace() null } } // 计算缩放比例 private fun calculateScaleFactor(width: Int, height: Int, maxSize: Int): Int { var scale = 1 while (width / scale >= maxSize || height / scale >= maxSize) { scale *= 2 } return scale }
调用方式
imageUri?.let { uri -> val compressedBase64 = uriToCompressedBase64(contentResolver, uri) compressedBase64?.let { val uploadRequest = UploadImageRequest(it) viewModelScope.launch { val response = apiService.uploadImage(uploadRequest) // 处理上传结果 } } }
内容的提问来源于stack exchange,提问作者Tmdhoon2
相关产品推荐
相关产品推荐

