[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(貌似需要FQ,把内容贴到下面)
总结如下:
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 a
304 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.