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

如何在Retrofit中压缩多张图片并通过Multipart.body上传解决超时问题

解决Retrofit上传大图片超时问题:图片压缩+Multipart上传方案

Hey there! I’ve run into this exact issue before—large images killing upload requests with timeouts is super common. Let’s break down how to compress those images first, then send them via Retrofit’s Multipart body smoothly.

第一步:实现图片压缩工具方法

你需要一个工具类来压缩图片,这里结合两种常用压缩方式:采样率压缩(降低分辨率)和质量压缩(减少文件体积但保留分辨率),平衡体积和清晰度。

Java示例代码

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class ImageCompressor {
    // 压缩图片到指定最大宽高和质量
    public static File compressImage(String imagePath, int maxWidth, int maxHeight, int quality) throws IOException {
        // 1. 先获取图片原始尺寸,计算采样率
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imagePath, options);
        
        // 计算合适的采样率,避免加载超大图占用内存
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
        options.inJustDecodeBounds = false;
        
        // 2. 解码图片
        Bitmap bitmap = BitmapFactory.decodeFile(imagePath, options);
        
        // 3. 创建临时目录存储压缩后的图片
        File tempDir = new File(Environment.getExternalStorageDirectory() + "/temp_compressed");
        if (!tempDir.exists()) tempDir.mkdirs();
        File compressedFile = new File(tempDir, System.currentTimeMillis() + ".jpg");
        
        // 4. 质量压缩并写入文件
        FileOutputStream outputStream = new FileOutputStream(compressedFile);
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
        outputStream.flush();
        outputStream.close();
        bitmap.recycle(); // 及时释放内存
        
        return compressedFile;
    }
    
    // 计算采样率的工具方法
    private static int calculateInSampleSize(BitmapFactory.Options options, int maxWidth, int maxHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        
        if (height > maxHeight || width > maxWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
            
            while ((halfHeight / inSampleSize) >= maxHeight && (halfWidth / inSampleSize) >= maxWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}

Kotlin示例代码

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Environment
import java.io.File
import java.io.FileOutputStream

object ImageCompressor {
    @Throws(Exception::class)
    fun compressImage(imagePath: String, maxWidth: Int, maxHeight: Int, quality: Int): File {
        val options = BitmapFactory.Options().apply {
            inJustDecodeBounds = true
        }
        BitmapFactory.decodeFile(imagePath, options)
        
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight)
        options.inJustDecodeBounds = false
        
        val bitmap = BitmapFactory.decodeFile(imagePath, options)
        
        val tempDir = File("${Environment.getExternalStorageDirectory()}/temp_compressed").apply {
            if (!exists()) mkdirs()
        }
        val compressedFile = File(tempDir, "${System.currentTimeMillis()}.jpg")
        
        FileOutputStream(compressedFile).use { outputStream ->
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
        }
        bitmap.recycle()
        
        return compressedFile
    }
    
    private fun calculateInSampleSize(options: BitmapFactory.Options, maxWidth: Int, maxHeight: Int): Int {
        var inSampleSize = 1
        val height = options.outHeight
        val width = options.outWidth
        
        if (height > maxHeight || width > maxWidth) {
            val halfHeight = height / 2
            val halfWidth = width / 2
            
            while (halfHeight / inSampleSize >= maxHeight && halfWidth / inSampleSize >= maxWidth) {
                inSampleSize *= 2
            }
        }
        return inSampleSize
    }
}

第二步:将压缩后的文件转为MultipartBody.Part

拿到压缩后的文件后,把每个文件转换成Retrofit需要的MultipartBody.Part对象,才能通过Multipart请求发送:

// 假设你有List<String> imagePaths存储原始图片路径
List<File> compressedFiles = new ArrayList<>();
for (String path : imagePaths) {
    try {
        // 这里设置最大宽高为1080,质量为70(可根据需求调整)
        File compressedFile = ImageCompressor.compressImage(path, 1080, 1080, 70);
        compressedFiles.add(compressedFile);
    } catch (IOException e) {
        e.printStackTrace();
        // 处理压缩失败的情况,比如跳过该图片或提示用户
    }
}

// 转换为MultipartBody.Part列表
List<MultipartBody.Part> imageParts = new ArrayList<>();
for (int i = 0; i < compressedFiles.size(); i++) {
    File file = compressedFiles.get(i);
    RequestBody requestBody = RequestBody.create(MediaType.parse("image/jpeg"), file);
    // "images[]"是服务器接收的字段名,要和后端开发人员约定好
    MultipartBody.Part part = MultipartBody.Part.createFormData("images[]", file.getName(), requestBody);
    imageParts.add(part);
}

第三步:定义Retrofit接口

确保你的Retrofit接口是Multipart类型,支持接收多个图片字段:

import retrofit2.Call;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
import java.util.List;

public interface UploadService {
    @Multipart
    @POST("your-upload-endpoint") // 替换成你的服务器上传接口地址
    Call<YourResponseModel> uploadImages(@Part List<MultipartBody.Part> images);
}

第四步:发起上传请求

创建Retrofit实例时建议调整超时时间(避免压缩后仍因网络慢超时),然后发起请求:

// 配置OkHttpClient,设置超时时间
OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(60, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS)
        .writeTimeout(60, TimeUnit.SECONDS)
        .build();

// 创建Retrofit实例
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("your-base-url") // 替换成你的服务器基础地址
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

// 发起请求
UploadService service = retrofit.create(UploadService.class);
Call<YourResponseModel> call = service.uploadImages(imageParts);
call.enqueue(new Callback<YourResponseModel>() {
    @Override
    public void onResponse(Call<YourResponseModel> call, Response<YourResponseModel> response) {
        // 处理成功响应
        // 记得删除临时压缩文件,避免占用用户存储空间
        for (File file : compressedFiles) {
            if (file.exists()) file.delete();
        }
    }

    @Override
    public void onFailure(Call<YourResponseModel> call, Throwable t) {
        // 处理失败情况
        t.printStackTrace();
        // 同样清理临时文件
        for (File file : compressedFiles) {
            if (file.exists()) file.delete();
        }
    }
});

额外建议

  • 调整压缩参数:根据业务需求调整maxWidth、maxHeight和quality,比如允许稍模糊的话,质量可降到50-60,文件体积会更小。
  • 权限处理:确保App已申请READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限(Android 10+可使用Scoped Storage,或申请MANAGE_EXTERNAL_STORAGE)。
  • 进度监听:如果需要显示上传进度,可以给OkHttpClient添加Interceptor来监听上传进度。

内容的提问来源于stack exchange,提问作者Harish Reddy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 10:14:07