volley源码解析(六)--HurlStack与HttpClientStack之争

Volley中网络加载有两种方式,分别是HurlStack与HttpClientStack,我们来看Volley.java中的一段代码

if (stack == null) {//如果没有限定stack
            if (Build.VERSION.SDK_INT >= 9) {//adk版本在9或者以上
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

由此可见,如果没有设置stack,则根据当前adk版本自动选择。在Android 2.2版本之前,HttpClient拥有较少的bug,因此使用它是最好的选择。

而在Android 2.3版本及以后,HttpURLConnection则是最佳的选择。它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。对于新的应用程序应该更加偏向于使用HttpURLConnection,因为在以后的工作当中我们也会将更多的时间放在优化HttpURLConnection上面。

为此,我们需要分别来看这两个类,在看这两个之前,我们先来看它们一个简单的父类HttpStack

/**
 * An HTTP stack abstraction.
 * 抽象的http栈
 */
public interface HttpStack {
    /**
     * Performs an HTTP request with the given parameters.
     * 根据参数,执行http请求
     * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
     * and the Content-Type header is set to request.getPostBodyContentType().</p>
     *
     * @param request the request to perform
     * @param additionalHeaders additional headers to be sent together with
     *         {@link Request#getHeaders()}
     * @return the HTTP response
     */
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError;

}

该父类主要规定了,子类必须有一个根据request请求数据,并且返回HttpResponse类的方法

OK,接下来我们先看HurlStack,这个类使用的是HttpURLConnection作为连接方式,在adk较高版本推荐使用(其实目前市场上2.3的系统已经很少见了)

我们直接看这个类的核心方法performRequest()

@Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);//开启连接
        for (String headerName : map.keySet()) {//添加请求参数
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);//设置请求方式
        // Initialize HttpResponse with data from the HttpURLConnection.
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);//http协议
        int responseCode = connection.getResponseCode();//获取响应状态
        if (responseCode == -1) {//-1说明没有响应,抛出异常
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());//响应状态类
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        response.setEntity(entityFromConnection(connection));//解析响应实体
        for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {//添加响应头
            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }
        return response;
    }

整个方法分成几个步骤,首先是将请求参数,存储到map当中

 HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);

然后是开启url连接

URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);//开启连接

来看openConnection()方法

/**
     * Opens an {@link HttpURLConnection} with parameters.
     * 开启网络连接
     * @param url
     * @return an open connection
     * @throws IOException
     */
    private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
        HttpURLConnection connection = createConnection(url);

        int timeoutMs = request.getTimeoutMs();
        connection.setConnectTimeout(timeoutMs);
        connection.setReadTimeout(timeoutMs);
        connection.setUseCaches(false);
        connection.setDoInput(true);

        // use caller-provided custom SslSocketFactory, if any, for HTTPS
        if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {//https
            ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
        }

        return connection;
    }

   /**
     * Create an {@link HttpURLConnection} for the specified {@code url}.
     */
    protected HttpURLConnection createConnection(URL url) throws IOException {
        return (HttpURLConnection) url.openConnection();
    }

这个方法主要就是调用url.openConnevtion()从而返回一个HttpURLConnection对象,其中的一些超时设置,是由request本身提供的

另外还根据url是否带有https,为HttpURLConnection设置setSSLSocketFactory(mSslSocketFactory对象是在构造方法中传入的)

得到HttpURLConnection,就设置请求参数

for (String headerName : map.keySet()) {//添加请求参数
            connection.addRequestProperty(headerName, map.get(headerName));
        }

然后是确定请求方式(GET,POST还是别的)

setConnectionParametersForRequest(connection, request);//设置请求方式

setConnectionParametersForRequest方法:

@SuppressWarnings("deprecation")
    /**
     * 设置请求方式
     * @param connection
     * @param request
     * @throws IOException
     * @throws AuthFailureError
     */
    /* package */
    static void setConnectionParametersForRequest(HttpURLConnection connection,
            Request<?> request) throws IOException, AuthFailureError {
        switch (request.getMethod()) {
            case Method.DEPRECATED_GET_OR_POST:
                // This is the deprecated way that needs to be handled for backwards compatibility.
                // If the request's post body is null, then the assumption is that the request is
                // GET.  Otherwise, it is assumed that the request is a POST.
                byte[] postBody = request.getPostBody();
                if (postBody != null) {
                    // Prepare output. There is no need to set Content-Length explicitly,
                    // since this is handled by HttpURLConnection using the size of the prepared
                    // output stream.
                    connection.setDoOutput(true);
                    connection.setRequestMethod("POST");
                    connection.addRequestProperty(HEADER_CONTENT_TYPE,
                            request.getPostBodyContentType());
                    DataOutputStream out = new DataOutputStream(connection.getOutputStream());
                    out.write(postBody);
                    out.close();
                }
                break;
            case Method.GET:
                // Not necessary to set the request method because connection defaults to GET but
                // being explicit here.
                connection.setRequestMethod("GET");
                break;
            case Method.DELETE:
                connection.setRequestMethod("DELETE");
                break;
            case Method.POST:
                connection.setRequestMethod("POST");
                addBodyIfExists(connection, request);
                break;
            case Method.PUT:
                connection.setRequestMethod("PUT");
                addBodyIfExists(connection, request);
                break;
            case Method.HEAD:
                connection.setRequestMethod("HEAD");
                break;
            case Method.OPTIONS:
                connection.setRequestMethod("OPTIONS");
                break;
            case Method.TRACE:
                connection.setRequestMethod("TRACE");
                break;
            case Method.PATCH:
                connection.setRequestMethod("PATCH");
                addBodyIfExists(connection, request);
                break;
            default:
                throw new IllegalStateException("Unknown method type.");
        }
    }

最后获取响应,将响应头信息包装成StatusLine对象,再包装成BasicHttpResponse对象

StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());//响应状态类
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);

然后为BasicHttpResponse加入响应内容

 response.setEntity(entityFromConnection(connection));//解析响应实体

entityFromConnection(HttpURLConnection connection)方法:

/**
     * Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}.
     * <br>解析出响应实体
     * @param connection
     * @return an HttpEntity populated with data from <code>connection</code>.
     */
    private static HttpEntity entityFromConnection(HttpURLConnection connection) {
        BasicHttpEntity entity = new BasicHttpEntity();
        InputStream inputStream;
        try {
            inputStream = connection.getInputStream();
        } catch (IOException ioe) {
            inputStream = connection.getErrorStream();
        }
        entity.setContent(inputStream);
        entity.setContentLength(connection.getContentLength());
        entity.setContentEncoding(connection.getContentEncoding());
        entity.setContentType(connection.getContentType());
        return entity;
    }

最后,加入响应头部内容

for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {//添加响应头
            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }

OK,这样就返回了一个具有完整信息的HttpResponse对象。整个过程比较简单,是常规的网络请求内容。

接下来我们看HttpClientStack的实现

同样,直接来看performRequest()方法

@Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
        addHeaders(httpRequest, additionalHeaders);//添加缓存头
        addHeaders(httpRequest, request.getHeaders());//添加请求头
        onPrepareRequest(httpRequest);//请求预处理
        HttpParams httpParams = httpRequest.getParams();
        int timeoutMs = request.getTimeoutMs();
        // TODO: Reevaluate this connection timeout based on more wide-scale
        // data collection and possibly different for wifi vs. 3G.
        HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
        HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
        return mClient.execute(httpRequest);
    }

请求步骤,首先是根据请求方式,构造HttpUriRequest对象,并且设置请求参数

HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);

createHttpRequest()方法:

/**
     * Creates the appropriate subclass of HttpUriRequest for passed in request.
     * 根据请求方式返回对应HttpUriRequest的子类
     */
    @SuppressWarnings("deprecation")
    /* protected */
    static HttpUriRequest createHttpRequest(Request<?> request,
            Map<String, String> additionalHeaders) throws AuthFailureError {
        switch (request.getMethod()) {
            case Method.DEPRECATED_GET_OR_POST: {
                // This is the deprecated way that needs to be handled for backwards compatibility.
                // If the request's post body is null, then the assumption is that the request is
                // GET.  Otherwise, it is assumed that the request is a POST.
                byte[] postBody = request.getPostBody();
                if (postBody != null) {
                    HttpPost postRequest = new HttpPost(request.getUrl());
                    postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType());
                    HttpEntity entity;
                    entity = new ByteArrayEntity(postBody);
                    postRequest.setEntity(entity);
                    return postRequest;
                } else {
                    return new HttpGet(request.getUrl());
                }
            }
            case Method.GET:
                return new HttpGet(request.getUrl());
            case Method.DELETE:
                return new HttpDelete(request.getUrl());
            case Method.POST: {
                HttpPost postRequest = new HttpPost(request.getUrl());
                postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
                setEntityIfNonEmptyBody(postRequest, request);//设置请求参数
                return postRequest;
            }
            case Method.PUT: {
                HttpPut putRequest = new HttpPut(request.getUrl());
                putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
                setEntityIfNonEmptyBody(putRequest, request);
                return putRequest;
            }
            case Method.HEAD:
                return new HttpHead(request.getUrl());
            case Method.OPTIONS:
                return new HttpOptions(request.getUrl());
            case Method.TRACE:
                return new HttpTrace(request.getUrl());
            case Method.PATCH: {
                HttpPatch patchRequest = new HttpPatch(request.getUrl());
                patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
                setEntityIfNonEmptyBody(patchRequest, request);
                return patchRequest;
            }
            default:
                throw new IllegalStateException("Unknown request method.");
        }
    }

从createHttpRequest()方法可以看出,在HttpClient中,只要根据请求方式,new一个HttpGet/HttpPost/....对象就可以了(而urlstack这一步是真的connnection而言的)

接着是为HttpUriRequest对象设置请求头部

 addHeaders(httpRequest, additionalHeaders);//添加缓存头
        addHeaders(httpRequest, request.getHeaders());//添加请求头

addHeaders方法:

/**
     * 添加响应头
     * @param httpRequest
     * @param headers
     */
    private static void addHeaders(HttpUriRequest httpRequest, Map<String, String> headers) {
        for (String key : headers.keySet()) {
            httpRequest.setHeader(key, headers.get(key));
        }
    }

最后,将HttpUriRequest对象交给httpClient执行

 return mClient.execute(httpRequest);

OK,HttpClientStack比我们想象的还要简单,起码比HurlStack简单,这是当然的,因为使用httpClient方式,其本质就是对urlConnection的封装,然而这个封装并不是很完美,所以造成了版本之间的差异。

到此为止,给大家介绍了HurlStack与HttpClientStack这两个类,同时也说明了真正的网络请求在哪里执行。

下一篇文章,将会来了解Response<T>的使用,Response<T>是Volley整个过程中,辗转获得的最终目的,作为响应实体,我们来看一下Response<T>是怎么设计的。

时间: 2025-01-02 17:43:33

volley源码解析(六)--HurlStack与HttpClientStack之争的相关文章

volley源代码解析(六)--HurlStack与HttpClientStack之争

Volley中网络载入有两种方式,各自是HurlStack与HttpClientStack.我们来看Volley.java中的一段代码 if (stack == null) {//假设没有限定stack if (Build.VERSION.SDK_INT >= 9) {//adk版本号在9或者以上 stack = new HurlStack(); } else { // Prior to Gingerbread, HttpUrlConnection was unreliable. // See:

Volley 源码解析&lt;转&gt;

Volley 源码解析 1. 功能介绍 1.1. Volley Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架.在 Google I/O 2013 大会上发布. 名字由来:a burst or emission of many things or a large amount at once发布演讲时候的配图 从名字由来和配图中无数急促的火箭可以看出 Volley 的特点:特别适合数据量小,通信频繁的网络操作.(个人认为 Android 应用中绝大多数的网

Android开发——Volley源码解析

0. 前言   其实写这篇文章只为一个目的,虽然Volley用起来很爽,但是面试官问你人家内部是怎么实现呢,没看过源码的话,在面试官眼里你和一个拿着一本Volley使用手册的高中生没啥区别.还是那句话说得好,会用一回事,深入理解又是另一回事了. 1.  Volley源码解析 1.1  Volley入口 Volley首先获取到的是RequestQueue实例.源码中则直接调用了newRequestQueue方法. public static RequestQueue newRequestQueue

Volley 源码解析(转自codeKK)

Volley 源码解析 本文为 Android 开源项目源码解析 中 Volley 部分项目地址:Volley,分析的版本:35ce778,Demo 地址:Volley Demo分析者:grumoon,校对者:huxian99.Trinea,校对状态:完成 1. 功能介绍 1.1. Volley Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架.在 Google I/O 2013 大会上发布. 名字由来:a burst or emission of many

Volley 源码解析

Volley 源码解析 本文为 Android 开源项目源码解析 中 Volley 部分项目地址:Volley,分析的版本:35ce778,Demo 地址:Volley Demo分析者:grumoon,校对者:huxian99.Trinea,校对状态:完成 1. 功能介绍 1.1. Volley Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架.在 Google I/O 2013 大会上发布. 名字由来:a burst or emission of many

Volley 源码解析(转)

项目:Volley,分析者:grumoon,校对者:Trinea 本文为 Android 开源项目源码解析 中 Volley 部分项目地址:Volley,分析的版本:35ce778,Demo 地址:Volley Demo分析者:grumoon,校对者:huxian99.Trinea,校对状态:完成 1. 功能介绍 1.1. Volley Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架.在 Google I/O 2013 大会上发布. 名字由来:a burs

volley源码解析(七)--最终目的之Response&lt;T&gt;

在上篇文章中,我们最终通过网络,获取到了HttpResponse对象 HttpResponse是android包里面的一个类,然后为了更高的扩展性,我们在BasicNetwork类里面看到,Volley将其包装成一个Volley自己的对象NetworkResponse 另外,在BasicNetwork类中我们也注意到,对HttpResponse包装成NetworkResponse的过程中,使用HttpResponse的Inputstream,将数据保存在一个byte[]数组中. BasicNet

Volley源码解析

Volley源码分为两部分 1.核心类库(主要以接口为主)   2.各接口实现类.工具类          第2部分以实现接口.提供工具为主,不影响数据流转,所以我们可以暂时先不管.看第1部分,很多Error.Log暂时先不看,比较外围.剩下的类,贴个UML如下 各接口&类解释Network:通过网络(HttpURLConnection或者HttpClient),发送请求并得到应答Cache:提供缓存功能,主要为get.put两个方法设置和获取缓存NetworkDispatcher:获取网络侧阻

Celery 源码解析六:Events 的实现

在 Celery 中,除了远程控制之外,还有一个元素可以让我们对分布式中的任务的状态有所掌控,而且从实际意义上来说,这个元素对 Celery 更为重要,这就是在本文中将要说到的 Event. 在 Celery 中,注册了很多的 Event,这些 Event 将会在 Task/Worker 的状态发生变化的时候被发出,然后被绑定的 Event 消费者(Receiver)所接受,绑定的 Event 消费者可以是一连串的回调函数,相信细心的同学在前面的源码解析过程中也有发现一些关于 event 的蛛丝