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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:22:33