android文件上传

来源:互联网 发布:有淘宝店铺可以直播吗 编辑:程序博客网 时间:2024/05/16 18:37

一 概述:

 1.这篇文章是基于volley框架,新加的文件上传的代码的分析
 2.主要是了解这个请求类:MultiPartRequest
 3.我下的这个volley库,上传时候有点小问题.
 

二 认识MultiPartRequest:

1.先贴源码

  private UploadMultipartEntity mMultipartEntity;
    /**
     * Default connection timeout for Multipart requests
     */
    public static final int TIMEOUT_MS = 60000;


    public MultiPartRequest(int method, String url, Listener<T> listener, ErrorListener errorlistener, LoadingListener loadingListener) {


        super(method, url, errorlistener);
        mListener = listener;
        
        mMultipartEntity = new UploadMultipartEntity();
        
        final ExecutorDelivery delivery = new ExecutorDelivery(new Handler(Looper.getMainLooper()));
        setLoadingListener(loadingListener);
        if (loadingListener != null) {
            mMultipartEntity.setListener(new ProgressListener() {
                long time = SystemClock.uptimeMillis();
                long count = -1;
                
                @Override
                public void transferred(long num) {
                    if (count == -1) {
                        count = mMultipartEntity.getContentLength();
                    }
                    // LogUtils.d("bacy", "upload->" + count + ",num->" + num);
                    long thisTime = SystemClock.uptimeMillis();
                    if (thisTime - time >= getRate() || count == num) {
                        time = thisTime;
                        delivery.postLoading(MultiPartRequest.this, count, num);
                    }
                }
            });
        }
        setRetryPolicy(new DefaultRetryPolicy(TIMEOUT_MS, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
    }


    @Override
    public String getBodyContentType() {
        return mMultipartEntity.getContentType().getValue();
    }


    @Override
    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);


    @Override
    protected void deliverResponse(T response) {


        mListener.onResponse(response);
    }


    /**
     * Get the protocol charset
     */
    public String getProtocolCharset() {
        
        return PROTOCOL_CHARSET;
    }
    
    public void addPart(String key, String value) {
        StringPart part = new StringPart(key, value, PROTOCOL_CHARSET);
        mMultipartEntity.addPart(part);
    }
    
    public void addPart(String key, File file) {
        FilePart part = new FilePart(key, file, null, null);
        mMultipartEntity.addPart(part);
    }


    public UploadMultipartEntity getMultipartEntity() {
        return mMultipartEntity;
    }

    在这个类中,定义了UploadMultipartEntity这个类是继承于MultipartEntity,只要把要上传的文件通过方法addPart加入就ok.为什么呢?
  1.首先了解volley库的整个运作流程:
  

三:先分析下整个流程:

  在网络请求时候,会调用BasicNetwork的performRequest方法,源码如下(只截取了一部分):
 public NetworkResponse performRequest(ResponseDelivery delivery, Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map<String, String> responseHeaders = Collections.emptyMap();
            try {
                if (!URLUtil.isNetworkUrl(request.getUrl())) {
                    return new NetworkResponse(responseContents);
                }
                // Gather headers.
                Map<String, String> headers = new HashMap<String, String>();
                addCacheHeaders(headers, request.getCacheEntry());
                httpResponse = mHttpStack.performRequest(request, headers);
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();

2.然后看这一行httpResponse = mHttpStack.performRequest(request, headers);
responseHeaders = convertHeaders(httpResponse.getAllHeaders());调用了mHttpStack用于网络请求的类
在这个方法里面调用了  setConnectionParametersForRequest(connection, request);
3.然后在这个方法里面,假如是用POST请求,那么,执行到:
 case Method.POST:
                connection.setRequestMethod("POST");
                addBodyIfExists(connection, request);
                break;
4.再看下addBodylfExists
private static void addBodyIfExists(HttpURLConnection connection, final Request<?> request)
            throws IOException, AuthFailureError {
        connection.setDoOutput(true);
        connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
        
        if (request instanceof MultiPartRequest) {
            final UploadMultipartEntity multipartEntity = ((MultiPartRequest<?>) request).getMultipartEntity();
            // 防止所有文件写到内存中
            connection.setFixedLengthStreamingMode((int)multipartEntity.getContentLength());
            multipartEntity.writeTo(connection.getOutputStream());
        } else {
            byte[] body = request.getBody();
            if (body != null) {
                DataOutputStream out = new DataOutputStream(connection.getOutputStream());
                out.write(body);
                out.close();
            }
        }
    }

 在这里获取了MultiPartRequest的UploadMultipartEntity成员变量,然后调用了writeTo方法.
然后回过头来看这个类UploadMultipartEntity他的方法为:
 @Override
    public void writeTo(OutputStream outstream) throws IOException {
        if (listener == null) {
            super.writeTo(outstream);
        } else {
            super.writeTo(new CountingOutputStream(outstream, offset, this.listener));
        }
    }
这里判断一下是否传入了listener,这个什么用呢,其实是用来计算进度的,也就是文件上传的百分比
然后先看      super.writeTo(outstream);
public void writeTo(final OutputStream out) throws IOException {
        if (out == null) {
            throw new IllegalArgumentException("Output stream may not be null");    //$NON-NLS-1$
        }
        for (Part part : parts) {
            part.writeTo(out, boundary);
        }
        out.write(boundary.getClosingBoundary());
        out.flush();
    }
 在这里就把那些添加的Part依次调用Part的WriteTo直接写入输出流,然后看下我们写入的文件部分,也就是FilePart类
他的writeTo方法如下:
public void writeTo(OutputStream out, Boundary boundary) throws IOException {
        out.write(getHeader(boundary));
        InputStream in = new FileInputStream(file);
        try {
            byte[] tmp = new byte[4096];
            int l;
            while ((l = in.read(tmp)) != -1) {
                out.write(tmp, 0, l);
            }
        } finally {
            in.close();
        }
        out.write(CRLF);
    }
看到这里,可能明白了,他就是最好按照文件的上传格式,先上传Boundary边界,然后再上传文件自己的流

四:自己找出的问题所在:

在获取这个边界的时候最后会运行到如下代码:
 private byte[] generateHeader(Boundary boundary) {
        if (headersProvider == null) {
            throw new RuntimeException("Uninitialized headersProvider");    //$NON-NLS-1$
        }
        final ByteArrayBuffer buf = new ByteArrayBuffer(256);
        append(buf, boundary.getStartingBoundary());
        append(buf, headersProvider.getContentDisposition());
        append(buf, CRLF);
        append(buf, headersProvider.getContentType());
        append(buf, CRLF);
       append(buf, headersProvider.getContentTransferEncoding());
       append(buf, CRLF);
        append(buf, CRLF);
        return buf.toByteArray();
    }
在服务器根本就不解释这一行,也即是append(buf, headersProvider.getContentTransferEncoding());
       append(buf, CRLF);,所以服务器解释流的地方就不对,文件就打不开,所以把这2句代码屏蔽掉就行了!!!(研究了半天。。。)

五:结束语:

先就写到这,那个用于进度的listener,相信聪明的大家一看就会。水平有限,但是努力追求技术的心是不会变的,不但要知其然,而且要知其所以然。顺便把volley上传下。。


1 0