Volley源码分析之自定义MultiPartRequest(文件上传)

本篇内容目录:

  • 使用HttpURLConnection上传文件到服务器案例

  • 自定义支持文件上传的MultiPartRequest

  • Web后台接收文件的部分代码

先来看下HttpURLConnection来文件上传的案例:

1.传送数据到服务器,必定是使用POST请求:

    //设置请求方式为post
    httpURLConnection.setDoOutput(true);
    httpURLConnection.setRequestMethod("POST");

2.上传文件的HTTP请求中的Content-Type:

在HTML中文件上传:

<form method="POST" enctype="multipart/form-data" action="fup.cgi">
  File to upload: <input type="file" name="upfile"><br/>
  Notes about the file: <input type="text" name="note"><br/>
  <br/>
  <input type="submit" value="Press"> to upload the file!
</form>

从上面可知,Content-Type的格式为multipart/form-data。无论是网页还是android app都是客户端,使用HttpURLConnection上传文件都是一致的。故,这里的Content-Type应该设置为:

multipart/form-data

3.来了解下multipart/form-data格式的数据:

这里,案例:一个名为file1的text文件和一个名为file2的gif文件,同时带有一段字符串(”Joe Blow”)共同在HTML中上传 。而在Http请求中显示的数据:

      Content-type: multipart/form-data, boundary=AaB03x 

        //普通数据
        --AaB03x
        content-disposition: form-data; name="field1"
        Joe Blow 

       //多个文件数据的格式
        --AaB03x
        content-disposition: form-data; name="pics"
        Content-type: multipart/mixed, boundary=BbC04y

        //file1.text的数据
        --BbC04y
        Content-disposition: attachment; filename="file1.txt"
        Content-Type: text/plain
        ... file1.txt 的内容...   

        //file2.gif的数据结构
        --BbC04y 
        Content-disposition: attachment; filename="file2.gif"
        Content-type: image/gif
        Content-Transfer-Encoding: binary
        ... file2.gif的内容... 

        --BbC04y-- 

        --AaB03x--    

从以上资料可知:multipart/form-data由多个部分组成,每一部分都有一个content-disposition标题头,它的值是”form-data”,它的属性指明了其在表单内的字段名。

举例来说,’content-disposition: form-data; name=”xxxxx”’,这里的xxxxx就是对应于该字段的字段名。

对所有的多部分MIME类型来说,每一部分有一个可选的Content-Type,默认的值是text/plain。如果知道是什么类型的话,就定义为相应的媒体类型。否则的话,就标识为application/octet-stream。

文件名可以由标题头”content-disposition”中的filename参数所指定。

总之,multipart/form-data的媒体内容遵从RFC 1521所规定的多部分的数据流规则。

以上关于multipart/form-data的资料来源于RFC文档目录, HTML中基于表单的文件上传

4.设置multipart/form-data格式的数据:

    private static final String BOUNDARY = "----------" + System.currentTimeMillis();
    /**
     * 请求的内容类型
     */
    private static final String PROTOCOL_CONTENT_TYPE = "multipart/form-data; boundary=" + BOUNDARY; 

    /**
     * 多个文件间的间隔
     */
    private static final String FILEINTERVAL = "\r\n";  

    /**
     * 获取到文件的head
     *
     * @return
     */
    public byte[] getFileHead(String fileName) {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("--");
            buffer.append(BOUNDARY);
            buffer.append("\r\n");
            buffer.append("Content-Disposition: form-data;name=\"media\";filename=\"");
            buffer.append(fileName);
            buffer.append("\"\r\n");
            buffer.append("Content-Type:application/octet-stream\r\n\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取文件的foot
     *
     * @return
     */
    public byte[] getFileFoot() {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("\r\n--");
            buffer.append(BOUNDARY);
            buffer.append("--\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            return null;
        }
    }

5.支持文件上传的HttpURLConnection的完整代码如下:

/**
 * Created by ${新根} on 2016/11/6.
 * 博客:http://blog.csdn.net/hexingen
 * <p/>
 * 用途:
 * 使用httpUrlConnection上传文件到服务器
*/
public class HttpUrlConnectionOpts {

    /**
     * 字符编码格式
     */
    private static final String PROTOCOL_CHARSET = "utf-8";
    private static final String BOUNDARY = "----------" + System.currentTimeMillis();
    /**
     * 请求的内容类型
     */
    private static final String PROTOCOL_CONTENT_TYPE = "multipart/form-data; boundary=" + BOUNDARY;

    /**
     * 多个文件间的间隔
     */
    private static final String FILEINTERVAL = "\r\n";

    public HttpUrlConnectionOpts() {

    }
    public void fileUpLoad(String url, Map<String, File> files) {

            HttpURLConnection connection = createMultiPartConnection(url);
            addIfParameter(connection, files);
            String responeContent = getResponeFromService(connection);
    }
    /**
     * 获取从服务器相应的数据
     *
     * @param connection
     * @return
     */
    public String getResponeFromService(HttpURLConnection connection) {
        String responeContent = null;
        BufferedReader bufferedReader = null;
        try {
            if (connection != null) {
                connection.connect();
                int responeCode = connection.getResponseCode();
                if (responeCode == 200) {
                    bufferedReader = new BufferedReader( 
                           new InputStreamReader(connection.getInputStream()));
                    String line;
                    StringBuffer stringBuffer = new StringBuffer();
                    while ((line = bufferedReader.readLine()) != null) {
                        stringBuffer.append(line);
                    }
                    responeContent = stringBuffer.toString();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
            responeContent = null;
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (connection != null) {
                    connection.disconnect();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return responeContent;
    }

    /**
     * 若是文件列表不为空,则将文件列表上传。
     *
     * @param connection
     * @param files
     */
    public void addIfParameter(HttpURLConnection connection, Map<String, File> files) {
        if (files != null && connection != null) {
            DataOutputStream dataOutputStream = null;
            try {
                dataOutputStream = new DataOutputStream(connection.getOutputStream());
                int i = 1;
                Set<Map.Entry<String, File>> set = files.entrySet();
                for (Map.Entry<String, File> fileEntry : set) {
                    byte[] contentHeader = getFileHead(fileEntry.getKey());
                    //添加文件的头部格式
                    dataOutputStream.write(contentHeader, 0, contentHeader.length);
                    //添加文件数据
                    readFileData(fileEntry.getValue(), dataOutputStream);
                    //添加文件间的间隔,若是一个文件则不用添加间隔。若是多个文件时,最后一个文件不用添加间隔。
                    if (set.size() > 1 && i < set.size()) {
                        i++;
                        dataOutputStream.write(FILEINTERVAL.getBytes(PROTOCOL_CHARSET));
                    }
                }
                //写入文件的尾部格式
                byte[] contentFoot = getFileFoot();
                dataOutputStream.write(contentFoot, 0, contentFoot.length);
                //刷新数据到流中
                dataOutputStream.flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (dataOutputStream != null) {
                        dataOutputStream.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 将file数据写入流中
     *
     * @param file
     * @param outputStream
     */
    public void readFileData(File file, OutputStream outputStream) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            byte[] bytes = new byte[1024];
            int length;
            while ((length = fileInputStream.read(bytes)) > 0) {
                outputStream.write(bytes, 0, length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 创建和设置HttpUrlConnection的内容格式为文件上传格式
     *
     * @param url
     * @return
     */
    public HttpURLConnection createMultiPartConnection(String url) {
        HttpURLConnection httpURLConnection = null;
        try {
            httpURLConnection = (HttpURLConnection) new URL(url).openConnection();
            //设置请求方式为post
            httpURLConnection.setDoOutput(true);
            httpURLConnection.setRequestMethod("POST");
            //设置不使用缓存
            httpURLConnection.setUseCaches(false);
            //设置数据字符编码格式
            httpURLConnection.setRequestProperty("Charsert", PROTOCOL_CHARSET);
            //设置内容上传类型(multipart/form-data),这步是关键
            httpURLConnection.setRequestProperty("Content-Type", PROTOCOL_CONTENT_TYPE);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return httpURLConnection;
    }


    /**
     * 获取到文件的head
     *
     * @return
     */
    public byte[] getFileHead(String fileName) {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("--");
            buffer.append(BOUNDARY);
            buffer.append("\r\n");
            buffer.append("Content-Disposition: form-data;name=\"media\";filename=\"");
            buffer.append(fileName);
            buffer.append("\"\r\n");
            buffer.append("Content-Type:application/octet-stream\r\n\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取文件的foot
     *
     * @return
     */
    public byte[] getFileFoot() {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("\r\n--");
            buffer.append(BOUNDARY);
            buffer.append("--\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            return null;
        }
    }
}

自定义支持文件上传的MultiPartRequest

这里不再详细讲述怎么自定义Request,如何设置Header,Body等,可以阅读 Volley源码分析之自定义GsonRequest(带header,coockie,Json参数,Gson解析)

在不修改HurlStack 源码的前提下,将file文件数据转成byte[],传入MultiPartRequest中。完整代码如下:

/**
 * Created by 新根 on 2016/8/9.
 * 用途:
 * 各种数据上传到服务器的内容格式:
 * <p/>
 * 文件上传(内容格式):multipart/form-data
 * String字符串传送(内容格式):application/x-www-form-urlencoded
 * json传递(内容格式):application/json
 */
public class MultiPartRequest<T> extends Request<T> {
    private  static  final  String TAG=MultiPartRequest.class.getSimpleName();
    /**
     * 解析后的实体类
     */
    private final Class<T> clazz;

    private final Response.Listener<T> listener;

    /**
     * 自定义header:
     */
    private Map<String, String> headers;
    private final Gson gson = new Gson();
    /**
     * 字符编码格式
     */
    private static final String PROTOCOL_CHARSET = "utf-8";

    private static final String BOUNDARY = "----------" + System.currentTimeMillis();
    /**
     * Content type for request.
     */
    private static final String PROTOCOL_CONTENT_TYPE = "multipart/form-data; boundary=" + BOUNDARY;

    /**
     * 文件列表。参数1是文件名,参数2是文件编码成的byte[]
     */
    private Map<String, byte[]> fileList;
    /**
     * 多个文件间的间隔
     */
    private static final String FILEINTERVAL = "\r\n";

    public MultiPartRequest(int method, String url,
                            Class<T> clazz,
                            Response.Listener<T> listener, Response.ErrorListener errorListenerr) {
        super(method, url, errorListenerr);
        this.clazz = clazz;
        this.listener = listener;
        headers = new HashMap<>();
        fileList = new HashMap<String, byte[]>();
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            String json = new String(
                    response.data,
                    HttpHeaderParser.parseCharset(response.headers));

            T t = gson.fromJson(json, clazz);
            return Response.success(t, HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T t) {
        listener.onResponse(t);
    }


    /**
     * 重写getHeaders(),添加自定义的header
     *
     * @return
     * @throws AuthFailureError
     */
    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return headers;
    }

    public Map<String, String> setHeader(String key, String content) {
        if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(content)) {
            headers.put(key, content);
        }
        return headers;
    }

    /**
     * 默认添加图片数据
     *
     * @param file
     */
    public void addFile(byte[] file) {
        if (file != null) {
            addFile(getFileName(), file);
        }
    }

    /**
     * 添加文件名和文件数据
     *
     * @param fileName
     * @param file
     */
    public void addFile(String fileName, byte[] file) {
        if (!TextUtils.isEmpty(fileName) && file != null) {
            Log.i(TAG,fileName+" fileName");
            fileList.put(fileName, file);
        }
    }


    /**
     * 重写Content-Type:设置为json
     */
    @Override
    public String getBodyContentType() {
        return PROTOCOL_CONTENT_TYPE;
    }

    /**
     * post参数类型
     */
    @Override
    public String getPostBodyContentType() {
        return getBodyContentType();
    }

    /**
     * post参数
     */
    @Override
    public byte[] getPostBody() throws AuthFailureError {

        return getBody();
    }

    /**
     * 将string编码成byte
     *
     * @return
     * @throws AuthFailureError
     */
    @Override
    public byte[] getBody() throws AuthFailureError {
        byte[] body;
        ByteArrayOutputStream outputStream = null;
        try {
            outputStream = new ByteArrayOutputStream();
            Set<Map.Entry<String, byte[]>> set = fileList.entrySet();
            Log.i(TAG,set.size()+"filesize");
            int i=1;
            for (Map.Entry entry : set) {
                //添加文件的头部格式
                writeByte(outputStream, getFileHead((String) entry.getKey()));
                //添加文件数据
                writeByte(outputStream, (byte[]) entry.getValue());
                //添加文件间的间隔
                if (set.size() > 1&&i<set.size()) {
                    i++;
                    Log.i(TAG,"添加文件间隔");
                    writeByte(outputStream, FILEINTERVAL.getBytes(PROTOCOL_CHARSET));
                }
            }
            writeByte(outputStream, getFileFoot());
            outputStream.flush();
            body = outputStream.toByteArray();
            return body == null ? null : body;
        } catch (Exception e) {
            return null;
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (Exception e) {

            }
        }
    }

    public void writeByte(ByteArrayOutputStream outputStream, byte[] bytes) {
        Log.i(TAG,bytes.length+"byte长度");
        outputStream.write(bytes, 0, bytes.length);
    }

    /**
     * 以当前时间为文件名,
     * 文件后缀".png"
     *
     * @return
     */
    private int fileEnd=1;
    public String getFileName() {
        ++fileEnd;
        StringBuilder stringBuilder=new StringBuilder();
        stringBuilder.append(new Date().getTime());
        stringBuilder.append(fileEnd);
        stringBuilder.append(".png");
        return stringBuilder.toString();
    }


    /**
     * 获取到文件的head
     *
     * @return
     */
    public byte[] getFileHead(String fileName) {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("--");
            buffer.append(BOUNDARY);
            buffer.append("\r\n");
            buffer.append("Content-Disposition: form-data;name=\"media\";filename=\"");
            buffer.append(fileName);
            buffer.append("\"\r\n");
            buffer.append("Content-Type:application/octet-stream\r\n\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取文件的foot
     *
     * @return
     */
    public byte[] getFileFoot() {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("\r\n--");
            buffer.append(BOUNDARY);
            buffer.append("--\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            return null;
        }
    }
}

这里存在一个比较大的问题,文件过大转成byte[]会导致内存溢出,推荐采用hook方式,让Request不走内存流,走磁盘文件流,详情请阅读,Android开发一个VolleyHelper库,Hook Volley方式,无入侵实现(Form表单、JSON、文件上传、文件下载)

Web后台接口部分接收文件上传的代码:将文件写入F盘中,然后返回文件路径给客户端。

/**
     * Commons FileUpload 方式:当个或者多个文件上传
     * @param request
     * @param response
     */
    @RequestMapping(value="/fileUpload",method=RequestMethod.POST)
    public void fileUpLoad(HttpServletRequest request,HttpServletResponse response){
                // Check that we have a file upload request
                boolean isFile=ServletFileUpload.isMultipartContent(request);
                JSONObject jsonObject=new JSONObject();
                if(isFile){
                  String result=    writeFile(request);
                  jsonObject.put("path", result);
                }else{
                      jsonObject.put("path", "这不是文件");
                }
                setResponse(response, jsonObject);
    }
    public static final String CACHEFILE="F:"+File.separator 
                       +"WebProject"+File.separator+"fileUploadBitmap"; 

    public String writeFile(HttpServletRequest request){ 

        DiskFileItemFactory diskFileItemFactory=new DiskFileItemFactory();
        //设置储存器的最大值(内存缓存值)
        diskFileItemFactory.setSizeThreshold(1000*1024);
        //设置超出部分的存储位置(临时存储位置)
        diskFileItemFactory.setRepository(new File("E:/"));
        // Create a new file upload handler
        ServletFileUpload  upload=new ServletFileUpload(diskFileItemFactory);
        upload.setSizeMax(1000*1024);
        String result=null;
        try {
            List<FileItem> items=   upload.parseRequest(request);
            System.out.println(items.size()+"个文件");

            for (FileItem  fileItem: items) {
                System.out.println(fileItem.getName());
                if(!fileItem.isFormField()){
                   String fileName=fileItem.getName();
                   File file=new File(CACHEFILE);
                   if(file!=null&&!file.exists()){
                       file.mkdir();
                   }
                    //将数据写入文件
                    if(fileName.lastIndexOf("\\")>=0){
                         file=new File(file.getAbsoluteFile()+File.separator 
                            +fileName.substring(fileName.lastIndexOf("\\")));
                    }else{
                        file=new File(file.getAbsoluteFile()+File.separator 
                               +fileName.substring(fileName.lastIndexOf("\\")+1));
                    }
                    //FileUpload 提供两种方式:一种是直接将内容写入文件中,一种是将内容写入IO流中
                    fileItem.write(file);
                     result +=file.getAbsolutePath();
                }else{
                    result= "这不是文件,是一个表单";
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
           result=  "发生异常";
        }
        return  result;
    }


    /**
     * 设置客户端返回值
     * @param response
     * @param jsonObject
     */
    public void setResponse(HttpServletResponse response,JSONObject jsonObject){
        try {
            response.setCharacterEncoding("utf-8");
            response.setContentType("charset=UTF-8");
            String result=jsonObject.toString();
            response.getWriter().write(result, 0, result.length());;
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

在Activity中使用图片上传功能:一些Volley的配置,阅读 Volley源码分析之自定义GsonRequest(带header,coockie,Json参数,Gson解析)

    /**
     * 将bitmap编码成byte[],然后上传到服务器
     *
     * @param bitmap
     */
    public void sendFileUploadRequest(byte[] bitmap) {
        System.out.print("开始上传");
        MultiPartRequest<JsonBean> request = new MultiPartRequest<>(Request.Method.POST,  
              "http://192.168.1.101:8080/SSMProject/file/fileUpload", JsonBean.class 
               , new Response.Listener<JsonBean>() { 

            @Override
            public void onResponse(JsonBean jsonBean) {
               path_tv.setText("bitmap存储在:"+jsonBean.path);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(MainActivity.this,  
                        volleyError.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
        request.addFile(bitmap);
        request.setRetryPolicy(new DefaultRetryPolicy(50000,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        request.setTag(TAG);
        VolleySingleton.getInstance().addToRequestQueue(request);
    }

项目运行结果:

1.先进行拍照操作,效果图如下:

这里写图片描述

2.然后点击文件上传,web后台接收到文件,将文件写入F盘中文件夹里。

这里写图片描述

3.app上获取到服务器返回文件路径,效果图如下:

这里写图片描述

项目代码:http://download.csdn.net/detail/hexingen/9681762

PS : 推荐使用 , Android开发一个VolleyHelper库,Hook Volley方式,无入侵实现(Form表单、JSON、文件上传、文件下载)

相关知识点阅读:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值