最近在参加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到我的应用的截图 :
Android中自定义MultipartEntity实现文件上传以及使用Volley库实现文件上传_第1张图片

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

更多相关文章

  1. android调用webservice方法,参数和返回值都用字符串
  2. android http通过post上传文件和提交参数(通过拼装协议)
  3. 20150602_Andriod 向窗体传递参数
  4. android post方式传递参数并获取返回数据代码
  5. Android通过SystemProperties类查看系统参数
  6. Android:Activity中onCreate方法的参数及用途
  7. Android 使用正则表达式验证邮箱格式是否正确
  8. android Activity 和 Service 之间 传参数
  9. android 获取本应用详细系统参数

随机推荐

  1. ubuntu 12.04 下安装android编译环境
  2. Android(安卓)Google应用移植时包依赖关
  3. cordova打包android apk
  4. Android下载完文件打开
  5. 给android设置代理
  6. android 存储操作 大小显示换算 kb mb KB
  7. Android(安卓)对.properties文件的读取
  8. android 开发 实例 下部主导航(1)
  9. Android(安卓)app widget 支持的Layout和
  10. android ArcGIS学习笔记一