Android通过Retrofit上传Room存储的多张图片报错如何解决
错误原因
该ProtocolException: expected X bytes but received Y异常本质是请求头中声明的Content-Length值和实际发送的请求体字节数不匹配,你的代码里主要有以下几个触发点:
- 文件请求体的MediaType配置错误:你给图片文件创建
RequestBody时错误传入了multipart/form-data,该类型是整个Multipart请求的类型,单图片文件的正确类型应为image/jpeg,类型错误会导致OkHttp计算请求体长度出错。 - 并发请求导致文件覆盖:for循环中同时发起多个异步上传请求,如果存在相同order的记录,前一个请求还未读完文件,后一个请求就覆盖写入了同一个文件,导致读取的文件内容大小和初始计算的长度不符。
- 异步回调变量引用错误:循环中使用的全局
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); } }
备用方案:必须写入本地文件的处理方式
如果业务需要保留本地临时文件,可做以下修改:
- 写入文件前先删除同名旧文件,避免残留内容导致文件大小错误
- 使用唯一文件名(比如拼接UUID、时间戳),避免并发写入覆盖
- 写完文件后强制同步文件属性,确保读取时大小计算正确
其他可选优化
如果修改后仍有问题,可升级OkHttp、Retrofit到最新稳定版,部分旧版本存在Content-Length计算的已知bug;如果有自定义请求拦截器,检查是否有修改请求体的逻辑导致长度变化。
内容的提问来源于stack exchange,提问作者Alan Muñoz
相关产品推荐
相关产品推荐

