Android Volley实现文件(非路径)上传至后端的方法问询
解决Volley直接上传文件内容而非路径的问题
我来帮你搞定这个问题!你现在的代码是把文件转成byte数组塞到JSON里,这种方式不仅效率低,后端也难处理。正确的做法是用Multipart/form-data请求来上传文件,这也是后端接收文件的标准方式。咱们一步步修改:
步骤1:自定义Volley的Multipart请求类
Volley默认没有提供Multipart请求,所以我们需要自己写一个,用来处理文本参数和文件的上传:
public class MultipartRequest extends Request<JSONObject> { private final Map<String, String> params; private final Map<String, File> fileParams; private final Response.Listener<JSONObject> listener; public MultipartRequest(String url, Map<String, String> params, Map<String, File> fileParams, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) { super(Method.POST, url, errorListener); this.params = params; this.fileParams = fileParams; this.listener = listener; } @Override protected Map<String, String> getParams() throws AuthFailureError { return params; } @Override public String getBodyContentType() { return "multipart/form-data; boundary=" + getBoundary(); } private String getBoundary() { return "VolleyMultipartBoundary" + System.currentTimeMillis(); } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); try { // 添加文本参数 for (Map.Entry<String, String> entry : params.entrySet()) { buildTextPart(dos, entry.getKey(), entry.getValue()); } // 添加文件参数 for (Map.Entry<String, File> entry : fileParams.entrySet()) { buildFilePart(dos, entry.getKey(), entry.getValue()); } // 结束边界 dos.writeBytes("--" + getBoundary() + "--\r\n"); dos.flush(); return bos.toByteArray(); } catch (IOException e) { VolleyLog.e("Error writing to ByteArrayOutputStream: %s", e.toString()); return null; } finally { try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } } private void buildTextPart(DataOutputStream dos, String key, String value) throws IOException { dos.writeBytes("--" + getBoundary() + "\r\n"); dos.writeBytes("Content-Disposition: form-data; name=\"" + key + "\"\r\n"); dos.writeBytes("\r\n"); dos.writeBytes(value + "\r\n"); } private void buildFilePart(DataOutputStream dos, String key, File file) throws IOException { dos.writeBytes("--" + getBoundary() + "\r\n"); dos.writeBytes("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\"\r\n"); dos.writeBytes("Content-Type: " + getMimeType(file.getAbsolutePath()) + "\r\n"); dos.writeBytes("\r\n"); FileInputStream fis = new FileInputStream(file); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { dos.write(buffer, 0, bytesRead); } fis.close(); dos.writeBytes("\r\n"); } private String getMimeType(String path) { String type = null; String extension = MimeTypeMap.getFileExtensionFromUrl(path); if (extension != null) { type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); } return type != null ? type : "application/octet-stream"; } @Override protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) { try { String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); return Response.success(new JSONObject(jsonString), HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException | JSONException e) { return Response.error(new ParseError(e)); } } @Override protected void deliverResponse(JSONObject response) { listener.onResponse(response); } }
步骤2:修改Activity中的文件存储逻辑
把原来存byte数组的逻辑改成存储File对象,这样后续上传更方便:
首先在Activity里添加成员变量:
private File attachmentFile, logoFile, isoFile;
然后修改onActivityResult方法:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 1000 && resultCode == RESULT_OK) { String filePath = data.getStringExtra(FilePickerActivity.RESULT_FILE_PATH); Attachment_tv.setText(filePath); attachmentFile = new File(filePath); } if (requestCode == 1002 && resultCode == RESULT_OK) { String filePath1 = data.getStringExtra(FilePickerActivity.RESULT_FILE_PATH); Logo_TV.setText(filePath1); logoFile = new File(filePath1); } if (requestCode == 1003 && resultCode == RESULT_OK) { String filePath2 = data.getStringExtra(FilePickerActivity.RESULT_FILE_PATH); ISO_TV.setText(filePath2); isoFile = new File(filePath2); } }
步骤3:替换上传请求为MultipartRequest
把原来的JsonObjectRequest换成我们自定义的MultipartRequest,同时整理文本参数和文件参数:
private void NextAdditional() { // 1. 整理所有文本参数 Map<String, String> textParams = new HashMap<>(); textParams.put("extraInfoId", ""); textParams.put("createdBy", ""); textParams.put("partyId", ""); textParams.put("notes", Note_Edit.getText().toString()); // 2. 处理certifications和documents的JSON数组(转成字符串传给后端) try { JSONArray certificationsArray = new JSONArray(); JSONObject certObj = new JSONObject(); certObj.put("id", ""); certObj.put("certificationType", ""); certificationsArray.put(certObj); textParams.put("certifications", certificationsArray.toString()); JSONArray documentsArray = new JSONArray(); JSONObject docObj = new JSONObject(); docObj.put("id", ""); docObj.put("documentType", ""); documentsArray.put(docObj); textParams.put("documents", documentsArray.toString()); } catch (JSONException e) { e.printStackTrace(); } // 3. 整理要上传的文件 Map<String, File> fileParams = new HashMap<>(); if (attachmentFile != null) { fileParams.put("documentfilepath", attachmentFile); // 键名要和后端接口一致 } if (logoFile != null) { fileParams.put("logoPath", logoFile); // 键名要和后端接口一致 } if (isoFile != null) { fileParams.put("certificationfilepath", isoFile); // 键名要和后端接口一致 } // 4. 创建并发送Multipart请求 mrequestqueue = Volley.newRequestQueue(AdditionalActivity.this); MultipartRequest multipartRequest = new MultipartRequest( url, textParams, fileParams, response -> { Log.d(TAG, response.toString()); Toast.makeText(AdditionalActivity.this, "Success", Toast.LENGTH_LONG).show(); }, error -> { Log.d(TAG, "Error: " + error.getMessage()); Toast.makeText(AdditionalActivity.this, "Failure", Toast.LENGTH_LONG).show(); }) { @Override public Map<String, String> getHeaders() throws AuthFailureError { HashMap<String, String> headers = new HashMap<>(); // 这里可以添加后端需要的头,比如Authorization,不需要设置Content-Type(MultipartRequest会自动处理) return headers; } }; mrequestqueue.add(multipartRequest); }
关键注意事项
- 后端适配:后端需要修改接口来接收Multipart请求,比如用Spring Boot的话,用
@RequestParam接收文本参数,@RequestPart接收文件。 - 参数名一致:
fileParams里的键名(比如documentfilepath)必须和后端接口的文件参数名完全匹配。 - 权限问题:如果是Android 10+,确保已经申请了文件访问权限(比如
READ_EXTERNAL_STORAGE或者MANAGE_EXTERNAL_STORAGE)。 - 文件大小:如果上传大文件,建议添加进度监听或者分块上传,避免OOM。
内容的提问来源于stack exchange,提问作者user9136665
相关产品推荐
相关产品推荐

