[Android]Volley源码分析(四)

上篇中有提到NetworkDispatcher是通过mNetwork(Network类型)来进行网络访问的,现在来看一下关于Network是如何进行网络访问的。

Network部分的类图:

Network有一个实现类BasicNetwork,它有一个mHttpStack的属性,实际的网络请求是由这个mHttpStack来进行的,看BasicNetwork的performRequest()方法,

 1  @Override
 2     public NetworkResponse performRequest(Request<?> request) throws VolleyError {
 3         long requestStart = SystemClock.elapsedRealtime();
 4         while (true) {
 5             HttpResponse httpResponse = null;
 6             byte[] responseContents = null;
 7             Map<String, String> responseHeaders = new HashMap<String, String>();
 8             try {
 9                 // Gather headers.
10                 Map<String, String> headers = new HashMap<String, String>();
11                 addCacheHeaders(headers, request.getCacheEntry());
12                 httpResponse = mHttpStack.performRequest(request, headers);
13                 StatusLine statusLine = httpResponse.getStatusLine();
14                 int statusCode = statusLine.getStatusCode();
15
16                 responseHeaders = convertHeaders(httpResponse.getAllHeaders());
17                 // Handle cache validation.
18                 if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
19                     return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
20                             request.getCacheEntry() == null ? null : request.getCacheEntry().data,
21                             responseHeaders, true);
22                 }
23
24                 // Some responses such as 204s do not have content.  We must check.
25                 if (httpResponse.getEntity() != null) {
26                   responseContents = entityToBytes(httpResponse.getEntity());
27                 } else {
28                   // Add 0 byte response as a way of honestly representing a
29                   // no-content request.
30                   responseContents = new byte[0];
31                 }
32
33                 // if the request is slow, log it.
34                 long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
35                 logSlowRequests(requestLifetime, request, responseContents, statusLine);
36
37                 if (statusCode < 200 || statusCode > 299) {
38                     throw new IOException();
39                 }
40                 return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
41             }              //一堆catch...      }

可以看到,在该方法中,实际是调用了mHttpStack.performRequest(request, headers);来进行网络访问,并对访问结果进行处理,貌似用的是Decorator模式。

如果状态码是304(HttpStatus.SC_NOT_MODIFIED)表示从上次访问后,服务器数据没有改变,则从Cache中拿数据。

【304(未修改)自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。如果网页自请求者上次请求后再也没有更改过,您应将服务器配置为返回此响应(称为 If-Modified-Since HTTP 标头)。服务器可以告诉客户端自从上次抓取后网页没有变更,进而节省带宽和开销。】

再来看HttpStack部分, HttpStack有两个实现类:HttpClientStack,HurlStack。分别通过HttpClient与HttpURLConnection来进行网络请求。

来看HttpClientStack的performRequest()方法

 1 @Override
 2     public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
 3             throws IOException, AuthFailureError {
 4         HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
 5         addHeaders(httpRequest, additionalHeaders);
 6         addHeaders(httpRequest, request.getHeaders());
 7         onPrepareRequest(httpRequest);
 8         HttpParams httpParams = httpRequest.getParams();
 9         int timeoutMs = request.getTimeoutMs();
10         // TODO: Reevaluate this connection timeout based on more wide-scale
11         // data collection and possibly different for wifi vs. 3G.
12         HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
13         HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
14         return mClient.execute(httpRequest);
15     }
16
17     /**
18      * Creates the appropriate subclass of HttpUriRequest for passed in request.
19      */
20     @SuppressWarnings("deprecation")
21     /* protected */ static HttpUriRequest createHttpRequest(Request<?> request,
22             Map<String, String> additionalHeaders) throws AuthFailureError {
23         switch (request.getMethod()) {
24             case Method.DEPRECATED_GET_OR_POST: {
25                 // This is the deprecated way that needs to be handled for backwards compatibility.
26                 // If the request‘s post body is null, then the assumption is that the request is
27                 // GET.  Otherwise, it is assumed that the request is a POST.
28                 byte[] postBody = request.getPostBody();
29                 if (postBody != null) {
30                     HttpPost postRequest = new HttpPost(request.getUrl());
31                     postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType());
32                     HttpEntity entity;
33                     entity = new ByteArrayEntity(postBody);
34                     postRequest.setEntity(entity);
35                     return postRequest;
36                 } else {
37                     return new HttpGet(request.getUrl());
38                 }
39             }
40             case Method.GET:
41                 return new HttpGet(request.getUrl());
42             case Method.DELETE:
43                 return new HttpDelete(request.getUrl());
44             case Method.POST: {
45                 HttpPost postRequest = new HttpPost(request.getUrl());
46                 postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
47                 setEntityIfNonEmptyBody(postRequest, request);
48                 return postRequest;
49             }
50             case Method.PUT: {
51                 HttpPut putRequest = new HttpPut(request.getUrl());
52                 putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
53                 setEntityIfNonEmptyBody(putRequest, request);
54                 return putRequest;
55             }
56             case Method.HEAD:
57                 return new HttpHead(request.getUrl());
58             case Method.OPTIONS:
59                 return new HttpOptions(request.getUrl());
60             case Method.TRACE:
61                 return new HttpTrace(request.getUrl());
62             case Method.PATCH: {
63                 HttpPatch patchRequest = new HttpPatch(request.getUrl());
64                 patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
65                 setEntityIfNonEmptyBody(patchRequest, request);
66                 return patchRequest;
67             }
68             default:
69                 throw new IllegalStateException("Unknown request method.");
70         }
71     }

通过request的mMethod属性来生成相应的HttpUriRequest, 然后通过成员属性mClient(HttpClient类型)的execute()方法来执行网络请求。

接着看HurlStack的performRequest()方法

 1 @Override
 2     public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
 3             throws IOException, AuthFailureError {
 4         String url = request.getUrl();
 5         HashMap<String, String> map = new HashMap<String, String>();
 6         map.putAll(request.getHeaders());
 7         map.putAll(additionalHeaders);
 8         if (mUrlRewriter != null) {
 9             String rewritten = mUrlRewriter.rewriteUrl(url);
10             if (rewritten == null) {
11                 throw new IOException("URL blocked by rewriter: " + url);
12             }
13             url = rewritten;
14         }
15         URL parsedUrl = new URL(url);
16         HttpURLConnection connection = openConnection(parsedUrl, request);
17         for (String headerName : map.keySet()) {
18             connection.addRequestProperty(headerName, map.get(headerName));
19         }
20         setConnectionParametersForRequest(connection, request);
21         // Initialize HttpResponse with data from the HttpURLConnection.
22         ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
23         int responseCode = connection.getResponseCode();
24         if (responseCode == -1) {
25             // -1 is returned by getResponseCode() if the response code could not be retrieved.
26             // Signal to the caller that something was wrong with the connection.
27             throw new IOException("Could not retrieve response code from HttpUrlConnection.");
28         }
29         StatusLine responseStatus = new BasicStatusLine(protocolVersion,
30                 connection.getResponseCode(), connection.getResponseMessage());
31         BasicHttpResponse response = new BasicHttpResponse(responseStatus);
32         response.setEntity(entityFromConnection(connection));
33         for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
34             if (header.getKey() != null) {
35                 Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
36                 response.addHeader(h);
37             }
38         }
39         return response;
40     }

也是根据request的mMethod属性来生成相应的HttpURLConnection,通过HttpURLConnection来进行网络请求。

那么什么时候使用HttpClientStack(即使用HttpClient),什么时候使用HurlStack(即使用HttpURLConnection)呢? 来看一个叫做Volley的工具类

 1 public class Volley {
 2
 3     /** Default on-disk cache directory. */
 4     private static final String DEFAULT_CACHE_DIR = "volley";
 5
 6     /**
 7      * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
 8      *
 9      * @param context A {@link Context} to use for creating the cache dir.
10      * @param stack An {@link HttpStack} to use for the network, or null for default.
11      * @return A started {@link RequestQueue} instance.
12      */
13     public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
14         File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
15
16         String userAgent = "volley/0";
17         try {
18             String packageName = context.getPackageName();
19             PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
20             userAgent = packageName + "/" + info.versionCode;
21         } catch (NameNotFoundException e) {
22         }
23
24         if (stack == null) {
25             if (Build.VERSION.SDK_INT >= 9) {
26                 stack = new HurlStack();
27             } else {
28                 // Prior to Gingerbread, HttpUrlConnection was unreliable.
29                 // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
30                 stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
31             }
32         }
33
34         Network network = new BasicNetwork(stack);
35
36         RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
37         queue.start();
38
39         return queue;
40     }
41
42     /**
43      * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
44      *
45      * @param context A {@link Context} to use for creating the cache dir.
46      * @return A started {@link RequestQueue} instance.
47      */
48     public static RequestQueue newRequestQueue(Context context) {
49         return newRequestQueue(context, null);
50     }
51 }

在它的新建一个请求队列的方法newRequestQueue(Context context, HttpStack stack)中,如果没有指定stack,则根据Android SDK的版本来决定使用HttpClientStack还是HurlStack。SDK版本大于等于9(9对应的应该是Android2.3-2.3.2 GingerBread)的时候使用的是HurlStack,小于9的时候使用的是HttpClientStack。

在第一篇的RequestManager中,初始化Volley是如下所示代码,所以得知,如果Android版本是2.3或以后的就使用HurlStack, 低于2.3版本的就使用HttpClientStack来进行网络请求。

1 public static void init(Context context) {
2         mRequestQueue = Volley.newRequestQueue(context);
3     }

至于为什么要这样做,可以参考http://www.eoeandroid.com/thread-314728-1-1.html或原文http://android-developers.blogspot.com/2011/09/androids-http-clients.html(貌似需要翻墙,把内容贴到下面)

总结如下:

1. HttpClient由于API繁多,所以很难在不破坏兼容性的情况下对它进行优化,所以Android团队对它的优化与改进不是很积极。

2. HttpURLConnection因为API简单,所以对它进行升级优化比较容易。

3. 在Froyo之前,HttpURLConnection有一些bug,尤其是对一个可读的InputStream调用close会污染整个连接池,使得只能通过禁用连接池来解决它。

4. 在Gingerbread中,HttpURLConnection会自动地将这个header  Accept-Encoding: gzip 加入请求并处理相应的经过压缩的响应。在Https连接方面也做了一些改进,HttpURLConnection会尝试通过SNI来进行连接, SNI可以使多个Https主机共享一个IP地址。如果连接失败,也能自动重试。

5. 在Ice Cream Sandwich中,增加了响应缓存。

6. 在Froyo或之前的版本中,最好使用HttpClient,因为它bug很少,而HttpURLConnection却有如3中的bug,Gingerbread或以上的版本,则应该使用HttpURLConnection,HttpURLConnection也是Android团队愿意花精力去优化与改进的。

Android’s HTTP Clients

[This post is by Jesse Wilson from the Dalvik team. —Tim Bray]

Most network-connected Android apps will use HTTP to send and receive data. Android includes two HTTP clients: HttpURLConnection and Apache HTTP Client. Both support HTTPS, streaming uploads and downloads, configurable timeouts, IPv6 and connection pooling.

Apache HTTP Client

DefaultHttpClient and its sibling AndroidHttpClient are extensible HTTP clients suitable for web browsers. They have large and flexible APIs. Their implementation is stable and they have few bugs.

But the large size of this API makes it difficult for us to improve it without breaking compatibility. The Android team is not actively working on Apache HTTP Client.

HttpURLConnection

HttpURLConnection is a general-purpose, lightweight HTTP client suitable for most applications. This class has humble beginnings, but its focused API has made it easy for us to improve steadily.

Prior to Froyo, HttpURLConnection had some frustrating bugs. In particular, calling close() on a readable InputStream couldpoison the connection pool. Work around this by disabling connection pooling:

private void disableConnectionReuseIfNecessary() {    // HTTP connection reuse which was buggy pre-froyo    if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {        System.setProperty("http.keepAlive", "false");    }}

In Gingerbread, we added transparent response compression. HttpURLConnection will automatically add this header to outgoing requests, and handle the corresponding response:

Accept-Encoding: gzip

Take advantage of this by configuring your Web server to compress responses for clients that can support it. If response compression is problematic, the class documentation shows how to disable it.

Since HTTP’s Content-Length header returns the compressed size, it is an error to use getContentLength() to size buffers for the uncompressed data. Instead, read bytes from the response until InputStream.read() returns -1.

We also made several improvements to HTTPS in Gingerbread. HttpsURLConnection attempts to connect with Server Name Indication (SNI) which allows multiple HTTPS hosts to share an IP address. It also enables compression and session tickets. Should the connection fail, it is automatically retried without these features. This makes HttpsURLConnection efficient when connecting to up-to-date servers, without breaking compatibility with older ones.

In Ice Cream Sandwich, we are adding a response cache. With the cache installed, HTTP requests will be satisfied in one of three ways:

  • Fully cached responses are served directly from local storage. Because no network connection needs to be made such responses are available immediately.
  • Conditionally cached responses must have their freshness validated by the webserver. The client sends a request like “Give me /foo.png if it changed since yesterday” and the server replies with either the updated content or a304 Not Modified status. If the content is unchanged it will not be downloaded!
  • Uncached responses are served from the web. These responses will get stored in the response cache for later.

Use reflection to enable HTTP response caching on devices that support it. This sample code will turn on the response cache on Ice Cream Sandwich without affecting earlier releases:

private void enableHttpResponseCache() {    try {        long httpCacheSize = 10 * 1024 * 1024; // 10 MiB        File httpCacheDir = new File(getCacheDir(), "http");        Class.forName("android.net.http.HttpResponseCache")            .getMethod("install", File.class, long.class)            .invoke(null, httpCacheDir, httpCacheSize);    } catch (Exception httpResponseCacheNotAvailable) {    }}

You should also configure your Web server to set cache headers on its HTTP responses.

Which client is best?

Apache HTTP client has fewer bugs on Eclair and Froyo. It is the best choice for these releases.

For Gingerbread and better, HttpURLConnection is the best choice. Its simple API and small size makes it great fit for Android. Transparent compression and response caching reduce network use, improve speed and save battery. New applications should use HttpURLConnection; it is where we will be spending our energy going forward.

[Android]Volley源码分析(四),布布扣,bubuko.com

时间: 2024-10-13 16:08:08

[Android]Volley源码分析(四)的相关文章

[Android]Volley源码分析(二)Cache

Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue newRequestQueue(Context conte

[Android]Volley源码分析(叁)Network

如果各位看官仔细看过我之前的文章,实际上Network这块的只是点小功能的补充.我们来看下NetworkDispatcher的核心处理逻辑: <span style="font-size:18px;">while (true) { try { // Take a request from the queue. request = mQueue.take(); } catch (InterruptedException e) { // We may have been int

[Android]Volley源码分析(肆)应用

通过前面的讲述,相信你已经对Volley的原理有了一定了解.本章将举一些我们能在应用中直接用到的例子,第一个例子是 NetworkImageView类,其实NetworkImageView顾名思义就是将异步的操作封装在了控件本身,这种设计可以充分保留控件的移植性和维护性.NetworkImageView通过调用setImageUrl来指定具体的url: public void setImageUrl(String url, ImageLoader imageLoader) { mUrl = ur

[Android] Volley源码分析(一)体系结构

Volley:google出的一个用于异步处理的框架.由于本身的易用性和良好的api,使得它能得以广泛的应用.我还是一如既往从源码的方向上来把控它.我们先通过一段简单的代码来了解Volley RequestQueue queue = Volley.newRequestQueue(this); ImageRequest imagerequest = new ImageRequest(url, new Response.Listener<Bitmap>(){ @Override public vo

[Android]Volley源码分析(五)

前面几篇通过源码分析了Volley是怎样进行请求调度及请求是如何被实际执行的,这篇最后来看下请求结果是如何交付给请求者的(一般是Android的UI主线程). 类图: 请求结果的交付是通过ResponseDelivery接口完成的,它有一个实现类ExecutorDelivery, 主要有postResponse()与postError()两个方法,分别在请求成功或失败时将结果提交给请求发起者. 1. 首先,在NetworkDispatcher的run()方法中,当服务器返回响应并解析完后,会调用

[Android]Volley源码分析(二)

上一篇介绍了Volley的使用,主要接触了Request与RequestQueue这两个类,这篇就来了解一下这两个类的具体实现. Request类图: Request类: Request是一个抽象类,其中的主要属性: mMethod: 请求方法,目前支持GET, POST, PUT, DELETE, HEAD, OPTIONS,TRACE, PATCH方法 mUrl: 请求Url mErrorListener: 错误处理监听器,请求出错时调用 mSequence: 请求的序号,相同优先级的请求在

[Android]Volley源码分析(一)

一. 如何使用Volley? 1. 首先定义一个RequestManager类,用来在Android程序启动时对Volley进行初始化.RequestManager为单例类,因为只有在程序启动时调用,所以不需要考虑并发问题. 1 /** 2 * Manager for the queue 3 */ 4 public class RequestManager { 5 6 /** 7 * 请求队列 8 */ 9 private static RequestQueue mRequestQueue; 1

[Android] Volley源码分析(五)答疑

Volley源码分析系列出了有一段日子了,有不少看官私底下给我留言,同时抛出了一些问题.对于一些比较简单的问题我们跳过去,这两天接到网友是@smali提出的问题.不得不赞一下这位看官看源码时候的细腻程度,我引出这个问题供大家一块思考一下. Q:在写入文件头数据的时候为何不直接写入Int而是通过移位的方式来完成? 我们来看一下对应的源码: writeInt(os, CACHE_MAGIC); static void writeInt(OutputStream os, int n) throws I

Android Volley源码分析

今天来顺手分析一下谷歌的volley http通信框架.首先从github上 下载volley的源码, 然后新建你自己的工程以后 选择import module 然后选择volley. 最后还需要更改1个 配置文件 就是我选中的那句话.记得要加.不然会报错.把volley作为一个module 在你的项目中引用的原因是,因为我们要分析源码,需要测试我们心中所想.所以这么做是最方便的. 就相当于eclipse里面的工程依赖. 有关于volley 如何使用的教程 我就不在这写了,请自行谷歌,我们直接看