Xutils HttpUtils上传文件的实现

今天解决了一个网络请求方面的bug, 在解决之余 熟悉了一下我们项目中网络请求用到的框架, 目前用的还是比较老的xUtils来处理http请求,闲暇之余对这部分流程进行了一番跟踪并做一下记录, 方便日后记忆学习


在理解Utils实现上传功能的流程之前, 需要对另外一个东东有所了解---HttpClient.java.  它是Apache http包里的一个类, 利用此类也可以实现上传文件的功能, 具体实现如下:


public void upload(String localFile){
         File file = new File(localFile);
         PostMethod filePost = new PostMethod(URL_STR);
         HttpClient client = new HttpClient();
         
         try {
             // 通过以下方法可以模拟页面参数提交
             filePost.setParameter("userName", userName);
             filePost.setParameter("passwd", passwd);
 
             Part[] parts = { new FilePart(file.getName(), file) };
             filePost.setRequestEntity(new MultipartRequestEntity(parts, filePost.getParams()));
             
             client.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
             
             int status = client.executeMethod(filePost);
             if (status == HttpStatus.SC_OK) {
                 System.out.println("上传成功");
             } else {
                 System.out.println("上传失败");
             }
         } catch (Exception ex) {
             ex.printStackTrace();
         } finally {
             filePost.releaseConnection();
         }
}

如上所述,通过HttpClient.executeMethod(PostMethod)的方式也可以实现上传文件的功能,  而在本文所讲的xUtils的上传功能实际上也是对HttpClient的封装, 提供统一接口,不必书写冗长的代码, 并添加了缓冲,重连,断点续传等功能。

以下代码是使用xUtils实现上传的demo

//实例化HttpUtils对象, 参数设置链接超时
HttpUtils HTTP_UTILS = new HttpUtils(60 * 1000);
//实例化RequestParams对象
RequestParams requestParams = new RequestParams();
//requestParams.setContentType("multipart/form-data");
StringBuilder picFileName = new StringBuilder();
picFileName.append(DeviceInfo.getInstance().getGetuiClientId())
           .append(NotifyUtil.getCurrentTime()).append(".png");
requestParams.addBodyParameter("picFileName", picFileName.toString());
//imageFile是File格式的对象, 将此File传递给RequestParams
requestParams.addBodyParameter("picFile", imageFile, "image/png");
requestParams.addBodyParameter("platform", "Android");
String photoUrl = Config.getUploadPhotoUrl();
//通过HTTP_UTILS来发送post请求, 并书写回调函数
HTTP_UTILS.send(HttpMethod.POST, url, params, new com.lidroid.xutils.http.callback.RequestCallBack<String>() {
            @Override
            public void onFailure(HttpException httpException, String arg1) {
                
            }

            @Override
            public void onSuccess(ResponseInfo<String> responseInfo) {
                
            }
        });
}

可以看到使用方法就是创建一个RequestParams与一个Url, 然后通过xUtils中的HttpUtils.send方法发送请求上传imageFile这个文件,并通过RequestCallBack这个类来回调处理返回的数据, 接下来就看一下具体流程:

首先看先HttpUtils.java

public HttpUtils(int connTimeout, String userAgent) {
    HttpParams params = new BasicHttpParams();

    ConnManagerParams.setTimeout(params, connTimeout);
    HttpConnectionParams.setSoTimeout(params, connTimeout);
    HttpConnectionParams.setConnectionTimeout(params, connTimeout);

    if (TextUtils.isEmpty(userAgent)) {
        userAgent = OtherUtils.getUserAgent(null);
    }
    HttpProtocolParams.setUserAgent(params, userAgent);

    ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRouteBean(10));
    ConnManagerParams.setMaxTotalConnections(params, 10);

    HttpConnectionParams.setTcpNoDelay(params, true);
    HttpConnectionParams.setSocketBufferSize(params, 1024 * 8);
    HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);

    SchemeRegistry schemeRegistry = new SchemeRegistry();
    schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    schemeRegistry.register(new Scheme("https", DefaultSSLSocketFactory.getSocketFactory(), 443));

    <span style="color:#FF0000;">//这个是核心类,最终就是调用这个HttpClient的方法去向服务端发送请求,上传文件</span>
    httpClient = new DefaultHttpClient(new ThreadSafeClientConnManager(params, schemeRegistry), params);

    httpClient.setHttpRequestRetryHandler(new RetryHandler(DEFAULT_RETRY_TIMES));

    httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
        @Override
        public void process(org.apache.http.HttpRequest httpRequest, HttpContext httpContext) throws org.apache.http.HttpException, IOException {
            if (!httpRequest.containsHeader(HEADER_ACCEPT_ENCODING)) {
                httpRequest.addHeader(HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
            }
        }
    });
    httpClient.addResponseInterceptor(new HttpResponseInterceptor() {
         @Override
         public void process(HttpResponse response, HttpContext httpContext) throws org.apache.http.HttpException, IOException {
             final HttpEntity entity = response.getEntity();
             if (entity == null) {
                 return;
             }
             final Header encoding = entity.getContentEncoding();
             if (encoding != null) {
                 for (HeaderElement element : encoding.getElements()) {
                     if (element.getName().equalsIgnoreCase("gzip")) {
                         response.setEntity(new GZipDecompressingEntity(response.getEntity()));
                         return;
                     }
                 }
             }
         }
     });
 }


就从RequestParams.java开始说起,它有几个重载的addBodyParameter的方法

当调用requestParams.addBodyParameter("picFile", imageFile, "image/png");时 看下具体代码:

<span style="font-size:18px;">public void addBodyParameter(String key, File file, String mimeType) {
    if (fileParams == null) {
        fileParams = new HashMap<String, ContentBody>();
    }
    fileParams.put(key, new FileBody(file, mimeType));
}</span>

fileParams是一个内部变量,声明是 private HashMap<String, ContentBody> fileParams; 可见,当传入的参数是File时,参数被保存在fileParams这个HashMap当中。OK至此, RequestParams的初始化已经结束。 接下来就是调用HttpUtils.send的方法来像服务端发送请求

<span style="font-size:18px;">public <T> HttpHandler<T> send(HttpRequest.HttpMethod method, String url, RequestParams params,
                               RequestCallBack<T> callBack) {
    if (url == null) throw new IllegalArgumentException("url may not be null");

    HttpRequest request = new HttpRequest(method, url);
    return sendRequest(request, params, callBack);
}</span>
通过method与url创建一个HttpRequest, 并调用sendRequest方法,首先来看一下这个HttpRequest

<span style="font-size:18px;">public HttpRequest(HttpMethod method, URI uri) {
    super();
    this.method = method;
    setURI(uri);
}</span>
将method和url传给内部变量,之后在调用client执行request的时候会用到这些参数, 接下来回到sendRequest方法:

<span style="font-size:18px;">private <T> HttpHandler<T> sendRequest(HttpRequest request, RequestParams params, RequestCallBack<T> callBack) {

    HttpHandler<T> handler = new HttpHandler<T>(httpClient, httpContext, responseTextCharset, callBack);

    handler.setExpiry(currentRequestExpiry);
    handler.setHttpRedirectHandler(httpRedirectHandler);
    request.setRequestParams(params, handler);

    if (params != null) {
        handler.setPriority(params.getPriority());
    }
    handler.executeOnExecutor(EXECUTOR, request);
    return handler;
}</span>
又冒出一个HttpHandler类, 创建它用到了4个参数, 其中第一个是httpClient, 这个client就是HttpUtils初始化的时候实例化的DefaultHttpClient的对象。

然后调用request.setRequestParams方法, 如下

<span style="font-size:18px;">public void setRequestParams(RequestParams param) {
    if (param != null) {
        if (uriCharset == null) {
            uriCharset = Charset.forName(param.getCharset());
        }
        List<RequestParams.HeaderItem> headerItems = param.getHeaders();
        if (headerItems != null) {
            for (RequestParams.HeaderItem headerItem : headerItems) {
                if (headerItem.overwrite) {
                    this.setHeader(headerItem.header);
                } else {
                    this.addHeader(headerItem.header);
                }
            }
        }
        this.addQueryStringParams(param.getQueryStringParams());
        this.setEntity(param.getEntity());
    }
}</span>
参数中的params就是我们调用HttpUtils.send方式之前,实例化的HttpParams对象, 至此 在HttpRequest对象中已经有method,url, 以及HttpParams的封装,最后调用handler.executeOnExecutor(EXECUTOR, request);来执行此request。 HttpHandler实现了PriorityAsyncTask, 你懂的,最终会调用doInBackground,看一下实现:

<span style="font-size:18px;">protected Void doInBackground(Object... params) {
    if (this.state == State.CANCELLED || params == null || params.length == 0) return null;

    if (params.length > 3) {
        fileSavePath = String.valueOf(params[1]);
        isDownloadingFile = fileSavePath != null;
        autoResume = (Boolean) params[2];
        autoRename = (Boolean) params[3];
    }

    try {
        if (this.state == State.CANCELLED) return null;
        // init request & requestUrl
        request = (HttpRequestBase) params[0];
        requestUrl = request.getURI().toString();
        if (callback != null) {
            callback.setRequestUrl(requestUrl);
        }

        this.publishProgress(UPDATE_START);

        lastUpdateTime = SystemClock.uptimeMillis();

        ResponseInfo<T> responseInfo = sendRequest(request);
        if (responseInfo != null) {
            this.publishProgress(UPDATE_SUCCESS, responseInfo);
            return null;
        }
    } catch (HttpException e) {
        this.publishProgress(UPDATE_FAILURE, e, e.getMessage());
    }

    return null;
}
</span>
params[0]就是传进来的HttpRequest, 之后调用sendRequest的方法先服务端发送请求, 并返回ResponseInfo的对象, 代码如下:

<span style="font-size:18px;">private ResponseInfo<T> sendRequest(HttpRequestBase request) throws HttpException {

    HttpRequestRetryHandler retryHandler = client.getHttpRequestRetryHandler();
    while (true) {

        if (autoResume && isDownloadingFile) {
            File downloadFile = new File(fileSavePath);
            long fileLen = 0;
            if (downloadFile.isFile() && downloadFile.exists()) {
                fileLen = downloadFile.length();
            }
            if (fileLen > 0) {
                request.setHeader("RANGE", "bytes=" + fileLen + "-");
            }
        }

        boolean retry = true;
        IOException exception = null;
        try {
            requestMethod = request.getMethod();
            if (HttpUtils.sHttpCache.isEnabled(requestMethod)) {
                String result = HttpUtils.sHttpCache.get(requestUrl);
                if (result != null) {
                    return new ResponseInfo<T>(null, (T) result, true);
                }
            }

            ResponseInfo<T> responseInfo = null;
            if (!isCancelled()) {
                HttpResponse response = client.execute(request, context);
                responseInfo = handleResponse(response);
            }
            return responseInfo;
        } catch (UnknownHostException e) {
            exception = e;
            retry = retryHandler.retryRequest(exception, ++retriedCount, context);
        } catch (IOException e) {
            exception = e;
            retry = retryHandler.retryRequest(exception, ++retriedCount, context);
        } catch (NullPointerException e) {
            exception = new IOException(e.getMessage());
            exception.initCause(e);
            retry = retryHandler.retryRequest(exception, ++retriedCount, context);
        } catch (HttpException e) {
            throw e;
        } catch (Throwable e) {
            exception = new IOException(e.getMessage());
            exception.initCause(e);
            retry = retryHandler.retryRequest(exception, ++retriedCount, context);
        }
        if (!retry) {
            throw new HttpException(exception);
        }
    }
}
</span>
先获取下client.getHttpRequestRetryHandler(),获取retry的设置; 如果使用了缓存则通过requestUrl去httpCache去获取,获取到了则创建ResponseInfo对象。如果没有缓存,调用httpClient执行http请求,获取到得结果交由handleResponse处理。


至此,上传流程基本结束,做一下总结吧:

1 创建并实例化HttpUtils类,  在构造方法中会实例化最核心的类DefaultHttpClient对象---此对象用来执行request,向服务端发送上传请求,并返回结果

2 创建并实例化HttpParams对象, 通过addBodyParameter方法将要上传的File传给HttpParams

3 创建并实例化HttpHandler对象, 将DefaultHttpClient的实例通过参数形式,传给HttpHandler

4 创建并实例化HttpRequest对象,在此对象中,封装好了method, url, 以及params, 然后通过HttpHandler对象来执行此request,并最终调用DefaultHttpClient来执行此request, 返回HttpResponse



你可能感兴趣的:(apache,android,upload,httputils,xutils)