Android中自定义MultipartEntity实现文件上传以及使用Volley库实现文件上传

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               


 最近在参加CSDN博客之星,希望大家给投一票,谢谢啦~                       点这里投我一票吧~

前言

 在开发当中,我们常常需要实现文件上传,比较常见的就是图片上传,比如修改个头像什么的。但是这个功能在Android和iOS中都没有默认的实现类,对于Android我们可以使用Apache提供的HttpClient.jar来实现这个功能,其中依赖的类就是Apache的httpmime.jar中的MultipartEntity这个类。我就是要实现一个文件上传功能,但是我还得下载一个jar包,而这个jar包几十KB,这尼玛仿佛并非人间!今天我们就来自己实现文件上传功能,并且弄懂它们的原理。

 在上一篇文章HTTP POST请求报文格式分析与Java实现文件上传中我们介绍了HTTP POST报文格式,如果有对POST报文格式不了解的同学可以先阅读这篇文章。


自定义实现MultipartEntity

 我们知道,使用网络协议传输数据无非就是要遵循某个协议,我们在开发移动应用时基本上都是使用HTTP协议。HTTP协议说白了就是基于TCP的一套网络请求协议,你根据该协议规定的格式传输数据,然后服务器返回给你数据。你的协议参数要是传递错了,那么服务器只能给你返回错误。

 这跟间谍之间对暗号有点相似,他们有一个规定的暗号,双方见面,A说: 天王盖地虎,B对: 宝塔镇河妖。对上了,说事;对不上,弄死这B。HTTP也是这样的,在HTTP请求时添加header和参数,服务器根据参数进行解析。形如 : 

POST /api/feed/ HTTP/1.1这里是header数据--分隔符参数1--分隔符参数2
 只要根据格式来向服务器发送请求就万事大吉了!下面我们就来看MultipartEntity的实现:

public class MultipartEntity implements HttpEntity {    private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"            .toCharArray();    /**     * 换行符     */    private final String NEW_LINE_STR = "\r\n";    private final String CONTENT_TYPE = "Content-Type: ";    private final String CONTENT_DISPOSITION = "Content-Disposition: ";    /**     * 文本参数和字符集     */    private final String TYPE_TEXT_CHARSET = "text/plain; charset=UTF-8";    /**     * 字节流参数     */    private final String TYPE_OCTET_STREAM = "application/octet-stream";    /**     * 二进制参数     */    private final byte[] BINARY_ENCODING = "Content-Transfer-Encoding: binary\r\n\r\n".getBytes();    /**     * 文本参数     */    private final byte[] BIT_ENCODING = "Content-Transfer-Encoding: 8bit\r\n\r\n".getBytes();    /**     * 分隔符     */    private String mBoundary = null;    /**     * 输出流     */    ByteArrayOutputStream mOutputStream = new ByteArrayOutputStream();    public MultipartEntity() {        this.mBoundary = generateBoundary();    }    /**     * 生成分隔符     *      * @return     */    private final String generateBoundary() {        final StringBuffer buf = new StringBuffer();        final Random rand = new Random();        for (int i = 0; i < 30; i++) {            buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);        }        return buf.toString();    }    /**     * 参数开头的分隔符     *      * @throws IOException     */    private void writeFirstBoundary() throws IOException {        mOutputStream.write(("--" + mBoundary + "\r\n").getBytes());    }    /**     * 添加文本参数     *      * @param key     * @param value     */    public void addStringPart(final String paramName, final String value) {        writeToOutputStream(paramName, value.getBytes(), TYPE_TEXT_CHARSET, BIT_ENCODING, "");    }    /**     * 将数据写入到输出流中     *      * @param key     * @param rawData     * @param type     * @param encodingBytes     * @param fileName     */    private void writeToOutputStream(String paramName, byte[] rawData, String type,            byte[] encodingBytes,            String fileName) {        try {            writeFirstBoundary();            mOutputStream.write((CONTENT_TYPE + type + NEW_LINE_STR).getBytes());            mOutputStream                    .write(getContentDispositionBytes(paramName, fileName));            mOutputStream.write(encodingBytes);            mOutputStream.write(rawData);            mOutputStream.write(NEW_LINE_STR.getBytes());        } catch (final IOException e) {            e.printStackTrace();        }    }    /**     * 添加二进制参数, 例如Bitmap的字节流参数     *      * @param key     * @param rawData     */    public void addBinaryPart(String paramName, final byte[] rawData) {        writeToOutputStream(paramName, rawData, TYPE_OCTET_STREAM, BINARY_ENCODING, "no-file");    }    /**     * 添加文件参数,可以实现文件上传功能     *      * @param key     * @param file     */    public void addFilePart(final String key, final File file) {        InputStream fin = null;        try {            fin = new FileInputStream(file);            writeFirstBoundary();            final String type = CONTENT_TYPE + TYPE_OCTET_STREAM + NEW_LINE_STR;            mOutputStream.write(getContentDispositionBytes(key, file.getName()));            mOutputStream.write(type.getBytes());            mOutputStream.write(BINARY_ENCODING);            final byte[] tmp = new byte[4096];            int len = 0;            while ((len = fin.read(tmp)) != -1) {                mOutputStream.write(tmp, 0, len);            }            mOutputStream.flush();        } catch (final IOException e) {            e.printStackTrace();        } finally {            closeSilently(fin);        }    }    private void closeSilently(Closeable closeable) {        try {            if (closeable != null) {                closeable.close();            }        } catch (final IOException e) {            e.printStackTrace();        }    }    private byte[] getContentDispositionBytes(String paramName, String fileName) {        StringBuilder stringBuilder = new StringBuilder();        stringBuilder.append(CONTENT_DISPOSITION + "form-data; name=\"" + paramName + "\"");        // 文本参数没有filename参数,设置为空即可        if (!TextUtils.isEmpty(fileName)) {            stringBuilder.append("; filename=\""                    + fileName + "\"");        }        return stringBuilder.append(NEW_LINE_STR).toString().getBytes();    }    @Override    public long getContentLength() {        return mOutputStream.toByteArray().length;    }    @Override    public Header getContentType() {        return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + mBoundary);    }    @Override    public boolean isChunked() {        return false;    }    @Override    public boolean isRepeatable() {        return false;    }    @Override    public boolean isStreaming() {        return false;    }    @Override    public void writeTo(final OutputStream outstream) throws IOException {        // 参数最末尾的结束符        final String endString = "--" + mBoundary + "--\r\n";        // 写入结束符        mOutputStream.write(endString.getBytes());        //        outstream.write(mOutputStream.toByteArray());    }    @Override    public Header getContentEncoding() {        return null;    }    @Override    public void consumeContent() throws IOException,            UnsupportedOperationException {        if (isStreaming()) {            throw new UnsupportedOperationException(                    "Streaming entity does not implement #consumeContent()");        }    }    @Override    public InputStream getContent() {        return new ByteArrayInputStream(mOutputStream.toByteArray());    }}

 用户可以通过 addStringPart、addBinaryPart、addFilePart来添加参数,分别表示添加字符串参数、添加二进制参数、添加文件参数。在MultipartEntity中有一个ByteArrayOutputStream对象,先将这些参数写到这个输出流中,当执行网络请求时,会执行
writeTo(final OutputStream outstream) 
  方法将所有参数的字节流数据写入到与服务器建立的TCP连接的输出流中,这样就将我们的参数传递给服务器了。当然在此之前,我们需要按照格式来向ByteArrayOutputStream对象中写数据。
 例如我要向服务器发送一个文本、一张bitmap图片、一个文件,即这个请求有三个参数。代码如下 : 

        MultipartEntity multipartEntity = new MultipartEntity();        // 文本参数        multipartEntity.addStringPart("type", "我的文本参数");        Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);        // 二进制参数        multipartEntity.addBinaryPart("images", bitmapToBytes(bmp));        // 文件参数        multipartEntity.addFilePart("images", new File("storage/emulated/0/test.jpg"));                // POST请求        HttpPost post = new HttpPost("url") ;        // 将multipartEntity设置给post        post.setEntity(multipartEntity);        // 使用http client来执行请求        HttpClient httpClient = new DefaultHttpClient() ;        httpClient.execute(post) ;

 MultipartEntity的输出格式会成为如下的格式 : 

POST /api/feed/ HTTP/1.1Content-Type: multipart/form-data; boundary=o3Fhj53z-oKToduAElfBaNU4pZhp4-User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.4.4; M040 Build/KTU84P)Host: www.myhost.comConnection: Keep-AliveAccept-Encoding: gzipContent-Length: 168518--o3Fhj53z-oKToduAElfBaNU4pZhp4-Content-Type: text/plain; charset=UTF-8Content-Disposition: form-data; name="type"Content-Transfer-Encoding: 8bitThis my type--o3Fhj53z-oKToduAElfBaNU4pZhp4-Content-Type: application/octet-streamContent-Disposition: form-data; name="images"; filename="no-file"Content-Transfer-Encoding: binary这里是bitmap的二进制数据--o3Fhj53z-oKToduAElfBaNU4pZhp4-Content-Type: application/octet-streamContent-Disposition: form-data; name="file"; filename="storage/emulated/0/test.jpg"Content-Transfer-Encoding: binary这里是图片文件的二进制数据--o3Fhj53z-oKToduAElfBaNU4pZhp4---

 看到很熟悉吧,这就是我们在文章开头时提到的POST报文格式。没错!HttpEntity就是负责将参数构造成HTTP的报文格式,文本参数该是什么格式、文件该是什么格式,什么类型,这些格式都是固定的。构造完之后,在执行请求时会将http请求的输出流通过writeTo(OutputStream) 函数传递进来,然后将这些参数数据全部输出到http输出流中即可。
 明白了这些道理,看看代码也就应该明白了吧。


Volley中实现文件上传

 Volley是Google官方推出的网络请求库,这个库很精简、优秀,但是他们也没有默认添加文件上传功能的支持。我们今天就来自定义一个Request实现文件上传功能,还是需要借助上面的MultipartEntity类,下面看代码:
/** * @author mrsimple */public class MultipartRequest extends Request<String> {    MultipartEntity mMultiPartEntity = new MultipartEntity();    Map<String, String> mHeaders = new HashMap<String, String>();    private final Listener<String> mListener;    /**     * Creates a new request with the given url.     *     * @param url URL to fetch the string at     * @param listener Listener to receive the String response     */    public MultipartRequest(String url, Listener<String> listener) {        this(url, listener, null);    }    /**     * Creates a new POST request.     *     * @param url URL to fetch the string at     * @param listener Listener to receive the String response     * @param errorListener Error listener, or null to ignore errors     */    public MultipartRequest(String url, Listener<String> listener, ErrorListener errorListener) {        super(Method.POST, url, errorListener);        mListener = listener;    }    /**     * @return     */    public MultipartEntity getMultiPartEntity() {        return mMultiPartEntity;    }    @Override    public String getBodyContentType() {        return mMultiPartEntity.getContentType().getValue();    }    public void addHeader(String key, String value) {        mHeaders.put(key, value);    }    @Override    public Map<String, String> getHeaders() throws AuthFailureError {        return mHeaders;    }    @Override    public byte[] getBody() {        ByteArrayOutputStream bos = new ByteArrayOutputStream();        try {            // multipart body            mMultiPartEntity.writeTo(bos);        } catch (IOException e) {            Log.e("", "IOException writing to ByteArrayOutputStream");        }        return bos.toByteArray();    }    @Override    protected Response<String> parseNetworkResponse(NetworkResponse response) {        String parsed = "";        try {            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));        } catch (UnsupportedEncodingException e) {            parsed = new String(response.data);        }        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));    }    @Override    protected void deliverResponse(String response) {        if (mListener != null) {            mListener.onResponse(response);        }    }}



使用示例代码: 
        RequestQueue queue = Volley.newRequestQueue(this);        MultipartRequest multipartRequest = new MultipartRequest(                "http://yourhost.com", new Listener<String>() {                    @Override                    public void onResponse(String response) {                        Log.e("", "### response : " + response);                    }                });        // 添加header        multipartRequest.addHeader("header-name", "value");        // 通过MultipartEntity来设置参数        MultipartEntity multi = multipartRequest.getMultiPartEntity();        // 文本参数        multi.addStringPart("location", "模拟的地理位置");        multi.addStringPart("type", "0");        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);        // 直接从上传Bitmap        multi.addBinaryPart("images", bitmapToBytes(bitmap));        // 上传文件        multi.addFilePart("imgfile", new File("storage/emulated/0/test.jpg"));        // 将请求添加到队列中        queue.add(multipartRequest);

效果图 

 这是我post到我的应用的截图 : 
                                                                      
          

 注意,MultipartRequest并不适合大文件上传,如果是大文件上传则需要分段上传,否则会出现OOM。最后给出 github链接
           

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_43661309/article/details/84102071
今日推荐