You need to enable JavaScript to run this app. Android通过Retrofit上传Room存储的多张图片报错如何解决
优惠活动
大模型
产品
解决方案
定价
更多

Android通过Retrofit上传Room存储的多张图片报错如何解决

错误原因

该ProtocolException: expected X bytes but received Y异常本质是请求头中声明的Content-Length值和实际发送的请求体字节数不匹配,你的代码里主要有以下几个触发点:

  1. 文件请求体的MediaType配置错误:你给图片文件创建RequestBody时错误传入了multipart/form-data,该类型是整个Multipart请求的类型,单图片文件的正确类型应为image/jpeg,类型错误会导致OkHttp计算请求体长度出错。
  2. 并发请求导致文件覆盖:for循环中同时发起多个异步上传请求,如果存在相同order的记录,前一个请求还未读完文件,后一个请求就覆盖写入了同一个文件,导致读取的文件内容大小和初始计算的长度不符。
  3. 异步回调变量引用错误:循环中使用的全局order变量是可变的,回调执行时order已经被循环修改,也会间接导致逻辑异常。

修复方案

最优方案:跳过本地文件写入,直接使用byte数组创建请求体

你本身已经从Room中拿到了图片的byte[]数据,完全不需要额外写入本地临时文件,既避免了文件IO的各类问题,也提升了运行效率:

修改循环中的上传逻辑

for (int i = 0; i < evidences.size(); i++) {
    // 定义final变量保存当前循环的订单和图片数据,避免异步回调取值错误
    final String currentOrder = evidences.get(i).getMobmx();
    final byte[] currentTicketEv = evidences.get(i).getTicketEv();

    Toast.makeText(this, "MOBMX: " + currentOrder, Toast.LENGTH_SHORT).show();

    // 直接用byte数组创建图片请求体,MediaType改为正确的image/jpeg
    RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpeg"), currentTicketEv);
    MultipartBody.Part body = MultipartBody.Part.createFormData("image", currentOrder + ".jpeg", requestFile);
    RequestBody orderBody = RequestBody.create(MediaType.parse("text/plain"), currentOrder);

    apiService.uploadPod(getString(R.string.uploadPodOfflinePetition), body, orderBody)
        .enqueue(new Callback<UserData>() {
            @Override
            public void onResponse(Call<UserData> call, Response<UserData> response) {
                if (response.isSuccessful()) {
                    if (response.body() != null) {
                        try {
                            // 传入当前循环的订单号,不要使用全局变量
                            processResult(response.body().getMsg(), response.body().getData(), currentOrder);
                        } catch (Exception e) {
                            Toast.makeText(OnlineEvidenceActivity.this, "Error procesando la respuesta del servidor, por favor intente de nuevo", Toast.LENGTH_SHORT).show();
                        }
                    }
                } else {
                    try {
                        String erroResp = response.errorBody().string();
                        Log.i("ERROR", erroResp);
                        processError(erroResp);
                    } catch (IOException | JSONException e) {
                        e.printStackTrace();
                        Toast.makeText(OnlineEvidenceActivity.this, "Error al procesar respuesta", Toast.LENGTH_SHORT).show();
                    }
                }
            }

            @Override
            public void onFailure(Call<UserData> call, Throwable t) {
                Log.e("ERROREVIDENCIA", t.getMessage());
            }
        });
}

修改processResult方法,增加订单号入参

private void processResult(String msg, Object data, String order) {
    StatusPODS statusPODS = new StatusPODS();
    if (msg.equals("ok")) {
        Toast.makeText(this, "Procesar evidencia", Toast.LENGTH_SHORT).show();
        statusPODS.setMobmx(order);
        statusPODS.setStatus("Exitoso");
        evidenceDAO.insertStatus(statusPODS);
    } else if (msg.equals("warning")) {
        Toast.makeText(this, "" + data, Toast.LENGTH_SHORT).show();
        statusPODS.setMobmx(order);
        statusPODS.setStatus("Warning");
        evidenceDAO.insertStatus(statusPODS);
    } else {
        Toast.makeText(this, "" + data, Toast.LENGTH_SHORT).show();
        statusPODS.setMobmx(order);
        statusPODS.setStatus("Error");
        evidenceDAO.insertStatus(statusPODS);
    }
}

备用方案:必须写入本地文件的处理方式

如果业务需要保留本地临时文件,可做以下修改:

  1. 写入文件前先删除同名旧文件,避免残留内容导致文件大小错误
  2. 使用唯一文件名(比如拼接UUID、时间戳),避免并发写入覆盖
  3. 写完文件后强制同步文件属性,确保读取时大小计算正确

其他可选优化

如果修改后仍有问题,可升级OkHttp、Retrofit到最新稳定版,部分旧版本存在Content-Length计算的已知bug;如果有自定义请求拦截器,检查是否有修改请求体的逻辑导致长度变化。

内容的提问来源于stack exchange,提问作者Alan Muñoz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.12 08:51:01