标签:
-----------------------------------------------------------------------------
转载:http://blog.csdn.net/crazy__chen/article/details/46506921
-----------------------------------------------------------------------------
上一篇文章当中,我介绍了CacheDispatcher和缓存类Cache是怎么根据request从缓存中获取到数据的,接下来这篇文章,将会介绍网络数据的获取。
对比缓存的获取,其实我们也有两个类,一个是NetworkDispatcher,一个是Network,前者是线程,后者是对抽象的网络的实体化。
这样的设计方式也是值得我们学习的,因为Network会负责处理网络请求过程中遇到的各种问题,这些问题在设计上来说,不应该和让NetworkDispatcher关心。NetworkDispatcher只是负责处理Network给出的Response既可以了。
下面我们先来看NetworkDispatcher
- public NetworkDispatcher(BlockingQueue<Request<?>> queue,
- Network network, Cache cache,
- ResponseDelivery delivery) {
- mQueue = queue;
- mNetwork = network;
- mCache = cache;
- mDelivery = delivery;
- }
与CacheDispatcher类似,必须传入网络,缓存,分发器和相应的队列。
请求队列mQueue是为了从中取出request,network是用于获取网络数据,cache是在获取到网络数据以后,可以把这些数据加入缓存。
至于分发器,就是为了讲response分发给对应的request。
接下来看run()方法
- @Override
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- while (true) {
- long startTimeMs = SystemClock.elapsedRealtime();
- Request<?> request;
- try {
-
- request = mQueue.take();
- } catch (InterruptedException e) {
-
- if (mQuit) {
- return;
- }
- continue;
- }
-
- try {
- request.addMarker("network-queue-take");
-
-
-
- if (request.isCanceled()) {
- request.finish("network-discard-cancelled");
- continue;
- }
-
- addTrafficStatsTag(request);
-
-
- NetworkResponse networkResponse = mNetwork.performRequest(request);
- request.addMarker("network-http-complete");
-
-
-
- if (networkResponse.notModified && request.hasHadResponseDelivered()) {
- request.finish("not-modified");
- continue;
- }
-
-
- Response<?> response = request.parseNetworkResponse(networkResponse);
- request.addMarker("network-parse-complete");
-
-
-
- if (request.shouldCache() && response.cacheEntry != null) {
- mCache.put(request.getCacheKey(), response.cacheEntry);
- request.addMarker("network-cache-written");
- }
-
-
- request.markDelivered();
- mDelivery.postResponse(request, response);
- } catch (VolleyError volleyError) {
- volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
- parseAndDeliverNetworkError(request, volleyError);
- } catch (Exception e) {
- VolleyLog.e(e, "Unhandled exception %s", e.toString());
- VolleyError volleyError = new VolleyError(e);
- volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
- mDelivery.postError(request, volleyError);
- }
- }
- }
run()方法流程如下:
1,从请求队列中取出请求request,交给network处理,network返回响应NetworkResponse。NetworkResponse是我们自己构造的类,用于代表网络响应。
2,判断NetworkResponse中的http状态码,如果304,表示资源没有被修改(说明我们之前已经请求过这个资源了),结束即可
3,否则,将NetworkResponse包装成本地Response,如果request要求缓存,则cache缓存
4,最后分发器将response分发给reuqest进行解析
上面的过程,可能大家会有一些疑惑。
首先是NetworkResponse类,其实这个类就代表一个网络响应,但是由于我们要面向抽象编程,就将这个NetworkResponse包装成我们自己定义的Response<T>类(这个类以后会介绍)。
对比缓存的那章,我们可以看到,Cache获取的其实是一个Entry实例,最后也是要包装成Response,再交给分发器分发。
这种思想有点类似适配器模式,是为了解决接口不统一的问题,对应request来说,针对的只是reponse,至于response的细节,reuqest也不想关心,只要它提供规定的接口就可以了。所以说response更像是一层包装,里面的细节可能很复杂很混乱,但是只要最后对外的接口是统一的既可以了。
值得我们注意的,还有这段代码对错误的处理。
还有我们可以看到,如果在数据请求过程中出错,所有的错误,都会被包装成VolleyError类,然后由分发器交给request处理。这里的思想和上面所说的一模一样,我就不再赘述了。
另外一个疑惑就是network具体是怎么从网络中获取数据的呢?这里就要求我们去了解这个类的源码了。
- public interface Network {
-
- public NetworkResponse performRequest(Request<?> request) throws VolleyError;
- }
从上面可以看到,network只是一个接口(类似cache),要求实现者必须实现performRequest()方法,下面我们它的具体实现BasicNetwork、
首先是一些简单的属性
- public class BasicNetwork implements Network {
-
- protected static final boolean DEBUG = VolleyLog.DEBUG;
-
- private static int SLOW_REQUEST_THRESHOLD_MS = 3000;
-
- private static int DEFAULT_POOL_SIZE = 4096;
-
- protected final HttpStack mHttpStack;
-
- protected final ByteArrayPool mPool;
前三个属性容易理解,后面两个HttpStack和ByteArrayPool则是我们没有见过的(其实HttpStack我在本专栏的开篇见到过,是Volley在创建请求队列的时候,要求传入的参数之一,也就是整个volley框架中很基本的设置之一,这里姑且就当大家没有见到,因为下篇文章会用整章来介绍它)。
我先来说明一下这个两个属性的作用吧。
HttpStack mHttpStack是实际的网络请求类,有的朋友可能会吐槽,怎么HttpStack是网络请求类,那么network又是干什么的呢?网络请求写在network里面不就好了吗?为什么把整个过程弄得更加复杂。
这是很好的问题。首先HttpStack的具体实现其实有两个版本(也就是说有两个子类,一个是用httpclient,一个是用urlConnetion),OK,为了我们可以选择性地使用这两种请求方式,设计HttpStack这个类是合理的。因为如果将两种方式都写到network里面,耦合就过重了。
另外,network作为网络的实体,处理的更多应该是网络问题,例如网络连接错误,地址重定向,401身份验证错误等;而HttpStack则不应该关心它们。
从这个设计的角度来看,分离出HttpStack也是合理的。
再看ByteArrayPool,这个类总的来说就是为io流读取和写入的时候提供缓冲区的。在java编程中,我在用io流写入的时候,经常会先申请一个byte[]来作为缓冲区,这个类的设计目的是为了减少缓存区的重复申请。
接下来,我们直接看BasicNetwork的performRequest()方法,看它是如何利用HttpStack来获取数据,以及如何处理和网络有关的错误的。
- @Override
-
- public NetworkResponse performRequest(Request<?> request) throws VolleyError {
- long requestStart = SystemClock.elapsedRealtime();
- while (true) {
- HttpResponse httpResponse = null;
- byte[] responseContents = null;
- Map<String, String> responseHeaders = Collections.emptyMap();
- try {
-
- 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();
-
- responseHeaders = convertHeaders(httpResponse.getAllHeaders());
-
- if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
-
- Entry entry = request.getCacheEntry();
- if (entry == null) {
- return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
- responseHeaders, true,
- SystemClock.elapsedRealtime() - requestStart);
- }
-
-
-
-
-
- entry.responseHeaders.putAll(responseHeaders);
- return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
- entry.responseHeaders, true,
- SystemClock.elapsedRealtime() - requestStart);
- }
-
-
- if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
- String newUrl = responseHeaders.get("Location");
- request.setRedirectUrl(newUrl);
- }
-
-
- if (httpResponse.getEntity() != null) {
- responseContents = entityToBytes(httpResponse.getEntity());
- } else {
-
-
- responseContents = new byte[0];
- }
-
-
- long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
- logSlowRequests(requestLifetime, request, responseContents, statusLine);
-
- if (statusCode < 200 || statusCode > 299) {
- throw new IOException();
- }
- return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
- SystemClock.elapsedRealtime() - requestStart);
- } catch (SocketTimeoutException e) {
- attemptRetryOnException("socket", request, new TimeoutError());
- } catch (ConnectTimeoutException e) {
- attemptRetryOnException("connection", request, new TimeoutError());
- } catch (MalformedURLException e) {
- throw new RuntimeException("Bad URL " + request.getUrl(), e);
- } catch (IOException e) {
- int statusCode = 0;
- NetworkResponse networkResponse = null;
- if (httpResponse != null) {
- statusCode = httpResponse.getStatusLine().getStatusCode();
- } else {
- throw new NoConnectionError(e);
- }
- if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
- statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
- VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());
- } else {
- VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
- }
- if (responseContents != null) {
- networkResponse = new NetworkResponse(statusCode, responseContents,
- responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
- if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
- statusCode == HttpStatus.SC_FORBIDDEN) {
- attemptRetryOnException("auth",
- request, new AuthFailureError(networkResponse));
- } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
- statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
- attemptRetryOnException("redirect",
- request, new AuthFailureError(networkResponse));
- } else {
-
- throw new ServerError(networkResponse);
- }
- } else {
- throw new NetworkError(networkResponse);
- }
- }
- }
- }
上面的代码一看非常复杂,大家跟着我的思路就可以了。其实网络数据请求,这里只是调用了
- httpResponse = mHttpStack.performRequest(request, headers);
OK,不理会细节的话,只要执行到这一句就看获得响应数据了。那么接下来的代码是做了什么,其实做了两件事情,一是根据http响应头,判断响应情况,二是处理异常和重试
首先是处理304(Not Modified)
- if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
-
- Entry entry = request.getCacheEntry();
- if (entry == null) {
- return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
- responseHeaders, true,
- SystemClock.elapsedRealtime() - requestStart);
- }
-
-
-
-
-
- entry.responseHeaders.putAll(responseHeaders);
- return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
- entry.responseHeaders, true,
- SystemClock.elapsedRealtime() - requestStart);
- }
由上面的代码知道,如果网络资源返回304,说明这个资源我们之前已经请求过了,而且没有修改。所以我们先判断entry,也就是缓存里面有没有(理论上来说应该是有的),如果没有,这说明该资源确实没有任何东西。
否则,用缓存中数据包装成NetworkResponse返回就可以了。
接下来是处理301,302重定向地址错误
- if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
- String newUrl = responseHeaders.get("Location");
- request.setRedirectUrl(newUrl);
- }
其实就是根据location返回的,需要重定向的地址,来修改request中的url
再接下来是处理204之类的,没有响应主体的错误
- if (httpResponse.getEntity() != null) {
- responseContents = entityToBytes(httpResponse.getEntity());
- } else {
-
-
- responseContents = new byte[0];
- }
其实就是没有内容的时候,也建造一个byte[0]数组
最后,凡是http头有以上错误的(304除外),抛出异常
- if (statusCode < 200 || statusCode > 299) {
- throw new IOException();
- }
是不是抛出异常就结束了呢?当然不是,因为volley还要根据request的RetryPolicy来进行重试,下面我们来看catch段的代码
- } catch (SocketTimeoutException e) {
- attemptRetryOnException("socket", request, new TimeoutError());
- } catch (ConnectTimeoutException e) {
- attemptRetryOnException("connection", request, new TimeoutError());
- } catch (MalformedURLException e) {
- throw new RuntimeException("Bad URL " + request.getUrl(), e);
- }
可以看到,连接超时,地址格式错误,这些错误都会调用attemptRetryOnException()方法来进行重试
- private static void attemptRetryOnException(String logPrefix, Request<?> request,
- VolleyError exception) throws VolleyError {
- RetryPolicy retryPolicy = request.getRetryPolicy();
- int oldTimeout = request.getTimeoutMs();
-
- try {
- retryPolicy.retry(exception);
- } catch (VolleyError e) {
- request.addMarker(
- String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
- throw e;
- }
- request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
- }
之前我们已经说过,retryPolicy.retry()其实就是做了一个自增操作,如果超出最大值,抛出异常来结束死循环(大家可以看到performRequest()方法里面是一个死循环,也就是说一个请求会不断重试,直到请求成功或者抛出异常)。
OK,重试策略实现了以后,我们再来看IOException是怎么处理的
- catch (IOException e) {
- int statusCode = 0;
- NetworkResponse networkResponse = null;
- if (httpResponse != null) {
- statusCode = httpResponse.getStatusLine().getStatusCode();
- } else {
- throw new NoConnectionError(e);
- }
- if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
- statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
- VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());
- } else {
- VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
- }
- if (responseContents != null) {
- networkResponse = new NetworkResponse(statusCode, responseContents,
- responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
- if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
- statusCode == HttpStatus.SC_FORBIDDEN) {
- attemptRetryOnException("auth",
- request, new AuthFailureError(networkResponse));
- } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
- statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
- attemptRetryOnException("redirect",
- request, new AuthFailureError(networkResponse));
- } else {
-
- throw new ServerError(networkResponse);
- }
- } else {
- throw new NetworkError(networkResponse);
- }
1,如果根本就没有响应内容httpResponse==null,说明网络连接有问题(也就是说手机没有联网),自己抛出异常结束
2,302,301错误,说明资源被重定向,做简单的记录
3,401,403错误,身份验证错误,重试
4,302,301错误,重试
OK,到此为止异常处理就已经说清楚了,这个network也说得比较清晰了。至于到底是怎么进行网络请求的,大家还是看下一篇文章吧。
volley介绍06
标签:
原文地址:http://www.cnblogs.com/aprz512/p/5316731.html