使用Volley与CodeIgniter上传Bitmap至服务器报错:未选择文件
嗨,这个问题我之前帮朋友排查过!核心原因是Volley自带的StringRequest默认不支持multipart/form-data格式的文件上传,而CodeIgniter的文件上传组件只认这种格式的请求,所以才会弹出“you did not select a file to upload”的报错。下面是具体的解决步骤:
解决Volley上传Bitmap到CodeIgniter的文件报错问题
第一步:自定义支持Multipart的Request类
Volley原生没有提供文件上传的请求类,我们需要自己写一个MultipartRequest来构造符合要求的请求体:
import com.android.volley.AuthFailureError; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.HttpHeaderParser; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Map; public class MultipartRequest extends Request<NetworkResponse> { private final Response.Listener<NetworkResponse> mListener; private final Response.ErrorListener mErrorListener; private final Map<String, String> mHeaders; private final Map<String, String> mParams; private final Map<String, DataPart> mFileParams; public MultipartRequest(int method, String url, Response.Listener<NetworkResponse> listener, Response.ErrorListener errorListener, Map<String, String> headers, Map<String, String> params, Map<String, DataPart> fileParams) { super(method, url, errorListener); this.mListener = listener; this.mErrorListener = errorListener; this.mHeaders = headers; this.mParams = params; this.mFileParams = fileParams; } @Override public Map<String, String> getHeaders() throws AuthFailureError { return mHeaders != null ? mHeaders : super.getHeaders(); } @Override public String getBodyContentType() { return "multipart/form-data; boundary=boundary"; } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream bos = new ByteArrayOutputStream(); String boundary = "boundary"; // 添加普通文本参数(如果有的话) if (mParams != null && !mParams.isEmpty()) { for (Map.Entry<String, String> entry : mParams.entrySet()) { try { bos.write(("--" + boundary + "\r\n").getBytes()); bos.write(("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n").getBytes()); bos.write((entry.getValue() + "\r\n").getBytes()); } catch (IOException e) { e.printStackTrace(); } } } // 添加Bitmap转成的文件参数 if (mFileParams != null && !mFileParams.isEmpty()) { for (Map.Entry<String, DataPart> entry : mFileParams.entrySet()) { DataPart dataPart = entry.getValue(); try { bos.write(("--" + boundary + "\r\n").getBytes()); bos.write(("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"; filename=\"" + dataPart.getFileName() + "\"\r\n").getBytes()); bos.write(("Content-Type: " + dataPart.getMimeType() + "\r\n\r\n").getBytes()); bos.write(dataPart.getContent()); bos.write("\r\n".getBytes()); } catch (IOException e) { e.printStackTrace(); } } } // 结束请求体边界 try { bos.write(("--" + boundary + "--\r\n").getBytes()); } catch (IOException e) { e.printStackTrace(); } return bos.toByteArray(); } @Override protected Response<NetworkResponse> parseNetworkResponse(NetworkResponse response) { return Response.success(response, HttpHeaderParser.parseCacheHeaders(response)); } @Override protected void deliverResponse(NetworkResponse response) { mListener.onResponse(response); } @Override public void deliverError(VolleyError error) { mErrorListener.onErrorResponse(error); } // 封装文件数据的内部类 public static class DataPart { private String fileName; private byte[] content; private String mimeType; public DataPart(String fileName, byte[] content, String mimeType) { this.fileName = fileName; this.content = content; this.mimeType = mimeType; } public String getFileName() { return fileName; } public byte[] getContent() { return content; } public String getMimeType() { return mimeType; } } }
第二步:修改你的uploadImage方法
把Bitmap转换成字节数组,用自定义的MultipartRequest发送请求:
public void uploadImage(final Context context, final Bitmap bitmap) { String url = "https://zenosama1111.000webhostapp.com/Upload/do_upload"; RequestQueue requestQueue = Volley.newRequestQueue(context); // 将Bitmap压缩成字节数组 ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 90, baos); byte[] imageBytes = baos.toByteArray(); // 构造文件参数,注意key要和后端接收的字段名一致(比如这里的"userfile") Map<String, MultipartRequest.DataPart> fileParams = new HashMap<>(); fileParams.put("userfile", new MultipartRequest.DataPart("uploaded_image.jpg", imageBytes, "image/jpeg")); // 如果需要传递普通参数,在这里添加 Map<String, String> params = new HashMap<>(); // params.put("user_id", "123"); MultipartRequest request = new MultipartRequest(Request.Method.POST, url, response -> { // 上传成功的处理逻辑 String responseStr = new String(response.data); Log.d("UploadResult", "Success: " + responseStr); }, error -> { // 上传失败的处理逻辑 Log.e("UploadError", "Failed: " + error.getMessage()); }, null, // 请求头可以传null,有需要再添加 params, fileParams); requestQueue.add(request); }
第三步:检查CodeIgniter后端配置
确保后端接收的字段名和Volley里的key一致,同时检查上传目录权限:
public function do_upload() { $config['upload_path'] = './uploads/'; // 确保这个目录存在且有写入权限 $config['allowed_types'] = 'gif|jpg|png|jpeg'; $config['max_size'] = 2048; // 限制最大2MB,可按需调整 $this->load->library('upload', $config); // 这里的"userfile"要和Volley里的fileParams的key完全一致 if (!$this->upload->do_upload('userfile')) { $error = array('error' => $this->upload->display_errors()); echo json_encode($error); } else { $data = array('upload_data' => $this->upload->data()); echo json_encode($data); } }
额外注意点:
- 服务器的
uploads目录必须设置写入权限(一般设为755或777,根据服务器环境调整) - 如果后端开启了CSRF验证,记得在请求头里添加CSRF Token参数
内容的提问来源于stack exchange,提问作者basaya
相关产品推荐
相关产品推荐

