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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 02:25:41