volley介绍06

Posted 力能扛鼎

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了volley介绍06相关的知识,希望对你有一定的参考价值。

-----------------------------------------------------------------------------

转载:http://blog.csdn.net/crazy__chen/article/details/46506921

-----------------------------------------------------------------------------

上一篇文章当中,我介绍了CacheDispatcher和缓存类Cache是怎么根据request从缓存中获取到数据的,接下来这篇文章,将会介绍网络数据的获取。

对比缓存的获取,其实我们也有两个类,一个是NetworkDispatcher,一个是Network,前者是线程,后者是对抽象的网络的实体化。

这样的设计方式也是值得我们学习的,因为Network会负责处理网络请求过程中遇到的各种问题,这些问题在设计上来说,不应该和让NetworkDispatcher关心。NetworkDispatcher只是负责处理Network给出的Response既可以了。

下面我们先来看NetworkDispatcher

 

[java] view plain copy
 
  1. /** 
  2.      * Creates a new network dispatcher thread.  You must call {@link #start()} 
  3.      * in order to begin processing. 
  4.      * 
  5.      * @param queue Queue of incoming requests for triage 
  6.      * @param network Network interface to use for performing requests 
  7.      * @param cache Cache interface to use for writing responses to cache 
  8.      * @param delivery Delivery interface to use for posting responses 
  9.      */  
  10.     public NetworkDispatcher(BlockingQueue<Request<?>> queue,  
  11.             Network network, Cache cache,  
  12.             ResponseDelivery delivery) {  
  13.         mQueue = queue;//请求队列  
  14.         mNetwork = network;//网络  
  15.         mCache = cache;//缓存  
  16.         mDelivery = delivery;//分发器  
  17.     }  

与CacheDispatcher类似,必须传入网络,缓存,分发器和相应的队列。

 

请求队列mQueue是为了从中取出request,network是用于获取网络数据,cache是在获取到网络数据以后,可以把这些数据加入缓存。

至于分发器,就是为了讲response分发给对应的request。

接下来看run()方法

 

[java] view plain copy
 
  1. @Override  
  2.     public void run() {  
  3.         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//设置线程优先级  
  4.         while (true) {  
  5.             long startTimeMs = SystemClock.elapsedRealtime();  
  6.             Request<?> request;  
  7.             try {  
  8.                 // Take a request from the queue.  
  9.                 request = mQueue.take();  
  10.             } catch (InterruptedException e) {  
  11.                 // We may have been interrupted because it was time to quit.  
  12.                 if (mQuit) {  
  13.                     return;  
  14.                 }  
  15.                 continue;  
  16.             }  
  17.   
  18.             try {  
  19.                 request.addMarker("network-queue-take");  
  20.   
  21.                 // If the request was cancelled already, do not perform the  
  22.                 // network request.  
  23.                 if (request.isCanceled()) {  
  24.                     request.finish("network-discard-cancelled");  
  25.                     continue;  
  26.                 }  
  27.   
  28.                 addTrafficStatsTag(request);//统计流量  
  29.   
  30.                 // Perform the network request. 从网络解析请求,获得响应  
  31.                 NetworkResponse networkResponse = mNetwork.performRequest(request);  
  32.                 request.addMarker("network-http-complete");  
  33.   
  34.                 // If the server returned 304 AND we delivered a response already,  
  35.                 // we‘re done -- don‘t deliver a second identical response.  
  36.                 if (networkResponse.notModified && request.hasHadResponseDelivered()) {  
  37.                     request.finish("not-modified");  
  38.                     continue;  
  39.                 }  
  40.   
  41.                 // Parse the response here on the worker thread. 解析网络响应到本地  
  42.                 Response<?> response = request.parseNetworkResponse(networkResponse);  
  43.                 request.addMarker("network-parse-complete");  
  44.   
  45.                 // Write to cache if applicable.  
  46.                 // TODO: Only update cache metadata instead of entire record for 304s.  
  47.                 if (request.shouldCache() && response.cacheEntry != null) {  
  48.                     mCache.put(request.getCacheKey(), response.cacheEntry);  
  49.                     request.addMarker("network-cache-written");  
  50.                 }  
  51.   
  52.                 // Post the response back.  
  53.                 request.markDelivered();  
  54.                 mDelivery.postResponse(request, response);  
  55.             } catch (VolleyError volleyError) {  
  56.                 volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);  
  57.                 parseAndDeliverNetworkError(request, volleyError);  
  58.             } catch (Exception e) {  
  59.                 VolleyLog.e(e, "Unhandled exception %s", e.toString());  
  60.                 VolleyError volleyError = new VolleyError(e);  
  61.                 volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);  
  62.                 mDelivery.postError(request, volleyError);  
  63.             }  
  64.         }  
  65.     }  

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具体是怎么从网络中获取数据的呢?这里就要求我们去了解这个类的源码了。

 

[java] view plain copy
 
  1. /** 
  2.  * An interface for performing requests. 
  3.  * 执行请求的网络,接口 
  4.  */  
  5. public interface Network {  
  6.     /** 
  7.      * Performs the specified request. 
  8.      * @param request Request to process 
  9.      * @return A {@link NetworkResponse} with data and caching metadata; will never be null 
  10.      * @throws VolleyError on errors 
  11.      * 执行对应的请求,返回响应 
  12.      */  
  13.     public NetworkResponse performRequest(Request<?> request) throws VolleyError;  
  14. }  

从上面可以看到,network只是一个接口(类似cache),要求实现者必须实现performRequest()方法,下面我们它的具体实现BasicNetwork、

 

首先是一些简单的属性

[java] view plain copy
 
  1. /** 
  2.  * A network performing Volley requests over an {@link HttpStack}. 
  3.  * 一个在httpstack上执行volley请求的网络  
  4.  */  
  5. public class BasicNetwork implements Network {  
  6.     /** 
  7.      * 是否debug 
  8.      */  
  9.     protected static final boolean DEBUG = VolleyLog.DEBUG;  
  10.     /** 
  11.      * 判断请求时间是否过长的标准 
  12.      */  
  13.     private static int SLOW_REQUEST_THRESHOLD_MS = 3000;  
  14.     /** 
  15.      * 默认缓冲池数 
  16.      */  
  17.     private static int DEFAULT_POOL_SIZE = 4096;  
  18.     /** 
  19.      * http执行栈 
  20.      */  
  21.     protected final HttpStack mHttpStack;  
  22.     /** 
  23.      * 缓冲数组池 
  24.      */  
  25.     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来获取数据,以及如何处理和网络有关的错误的。

[java] view plain copy
 
  1. @Override  
  2.     /** 
  3.      * 执行对应的请求,返回响应 
  4.      */  
  5.     public NetworkResponse performRequest(Request<?> request) throws VolleyError {  
  6.         long requestStart = SystemClock.elapsedRealtime();//请求开始执行时间  
  7.         while (true) {  
  8.             HttpResponse httpResponse = null;  
  9.             byte[] responseContents = null;//响应内容  
  10.             Map<String, String> responseHeaders = Collections.emptyMap();  
  11.             try {  
  12.                 // Gather headers.收集http头  
  13.                 Map<String, String> headers = new HashMap<String, String>();  
  14.                 addCacheHeaders(headers, request.getCacheEntry());//添加缓存中的头  
  15.                 httpResponse = mHttpStack.performRequest(request, headers);//执行请求,获取响应  
  16.                 StatusLine statusLine = httpResponse.getStatusLine();//http响应状态  
  17.                 int statusCode = statusLine.getStatusCode();//http标准回应信息  
  18.   
  19.                 responseHeaders = convertHeaders(httpResponse.getAllHeaders());//将响应头数组转换成map形式s  
  20.                 // Handle cache validation.  
  21.                 if (statusCode == HttpStatus.SC_NOT_MODIFIED) {//304,自从上次请求后,请求的网页未修改过  
  22.   
  23.                     Entry entry = request.getCacheEntry();  
  24.                     if (entry == null) {  
  25.                         return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,  
  26.                                 responseHeaders, true,  
  27.                                 SystemClock.elapsedRealtime() - requestStart);  
  28.                     }  
  29.   
  30.                     // A HTTP 304 response does not have all header fields. We  
  31.                     // have to use the header fields from the cache entry plus  
  32.                     // the new ones from the response.  
  33.                     // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5  
  34.                     entry.responseHeaders.putAll(responseHeaders);  
  35.                     return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,  
  36.                             entry.responseHeaders, true,  
  37.                             SystemClock.elapsedRealtime() - requestStart);  
  38.                 }  
  39.                   
  40.                 // Handle moved resources 网络资源被移动(301,302),修改重定向地址  
  41.                 if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {  
  42.                     String newUrl = responseHeaders.get("Location");  
  43.                     request.setRedirectUrl(newUrl);  
  44.                 }  
  45.   
  46.                 // Some responses such as 204s do not have content.  We must check.  
  47.                 if (httpResponse.getEntity() != null) {//返回响应主体  
  48.                   responseContents = entityToBytes(httpResponse.getEntity());//将主体转换byte[]形式  
  49.                 } else {//没有返回内容  
  50.                   // Add 0 byte response as a way of honestly representing a  
  51.                   // no-content request.  
  52.                   responseContents = new byte[0];  
  53.                 }  
  54.   
  55.                 // if the request is slow, log it.如果请求时间过长,记录之  
  56.                 long requestLifetime = SystemClock.elapsedRealtime() - requestStart;  
  57.                 logSlowRequests(requestLifetime, request, responseContents, statusLine);  
  58.   
  59.                 if (statusCode < 200 || statusCode > 299) {//网络错误  
  60.                     throw new IOException();  
  61.                 }  
  62.                 return new NetworkResponse(statusCode, responseContents, responseHeaders, false,  
  63.                         SystemClock.elapsedRealtime() - requestStart);  
  64.             } catch (SocketTimeoutException e) {  
  65.                 attemptRetryOnException("socket", request, new TimeoutError());  
  66.             } catch (ConnectTimeoutException e) {  
  67.                 attemptRetryOnException("connection", request, new TimeoutError());  
  68.             } catch (MalformedURLException e) {  
  69.                 throw new RuntimeException("Bad URL " + request.getUrl(), e);  
  70.             } catch (IOException e) {  
  71.                 int statusCode = 0;  
  72.                 NetworkResponse networkResponse = null;  
  73.                 if (httpResponse != null) {//没有响应,说明网络连接有问题  
  74.                     statusCode = httpResponse.getStatusLine().getStatusCode();  
  75.                 } else {  
  76.                     throw new NoConnectionError(e);  
  77.                 }  
  78.                 if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||   
  79.                         statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {//302,301  
  80.                     VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());  
  81.                 } else {  
  82.                     VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());  
  83.                 }  
  84.                 if (responseContents != null) {//有响应内容  
  85.                     networkResponse = new NetworkResponse(statusCode, responseContents,  
  86.                             responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);  
  87.                     if (statusCode == HttpStatus.SC_UNAUTHORIZED ||  
  88.                             statusCode == HttpStatus.SC_FORBIDDEN) {//401,请求要求身份验证;403,服务器拒绝请求  
  89.                         attemptRetryOnException("auth",//尝试重新请求  
  90.                                 request, new AuthFailureError(networkResponse));  
  91.                     } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||   
  92.                                 statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {//302,301  
  93.                         attemptRetryOnException("redirect",//尝试重定向请求  
  94.                                 request, new AuthFailureError(networkResponse));  
  95.                     } else {  
  96.                         // TODO: Only throw ServerError for 5xx status codes.  
  97.                         throw new ServerError(networkResponse);//抛出服务器异常  
  98.                     }  
  99.                 } else {  
  100.                     throw new NetworkError(networkResponse);  
  101.                 }  
  102.             }  
  103.         }  
  104.     }  

上面的代码一看非常复杂,大家跟着我的思路就可以了。其实网络数据请求,这里只是调用了

 

 

[java] view plain copy
 
  1. httpResponse = mHttpStack.performRequest(request, headers);//执行请求,获取响应  

OK,不理会细节的话,只要执行到这一句就看获得响应数据了。那么接下来的代码是做了什么,其实做了两件事情,一是根据http响应头,判断响应情况,二是处理异常和重试

 

首先是处理304(Not Modified)

 

[java] view plain copy
 
  1. // Handle cache validation.  
  2.                 if (statusCode == HttpStatus.SC_NOT_MODIFIED) {//304,自从上次请求后,请求的网页未修改过  
  3.   
  4.                     Entry entry = request.getCacheEntry();  
  5.                     if (entry == null) {  
  6.                         return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,  
  7.                                 responseHeaders, true,  
  8.                                 SystemClock.elapsedRealtime() - requestStart);  
  9.                     }  
  10.   
  11.                     // A HTTP 304 response does not have all header fields. We  
  12.                     // have to use the header fields from the cache entry plus  
  13.                     // the new ones from the response.  
  14.                     // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5  
  15.                     entry.responseHeaders.putAll(responseHeaders);  
  16.                     return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,  
  17.                             entry.responseHeaders, true,  
  18.                             SystemClock.elapsedRealtime() - requestStart);  
  19.                 }  

由上面的代码知道,如果网络资源返回304,说明这个资源我们之前已经请求过了,而且没有修改。所以我们先判断entry,也就是缓存里面有没有(理论上来说应该是有的),如果没有,这说明该资源确实没有任何东西。

 

否则,用缓存中数据包装成NetworkResponse返回就可以了。

接下来是处理301,302重定向地址错误

 

[java] view plain copy
 
  1. // Handle moved resources 网络资源被移动(301,302),修改重定向地址  
  2.                 if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {  
  3.                     String newUrl = responseHeaders.get("Location");  
  4.                     request.setRedirectUrl(newUrl);  
  5.                 }  

其实就是根据location返回的,需要重定向的地址,来修改request中的url

 

再接下来是处理204之类的,没有响应主体的错误

 

[java] view plain copy
 
  1. // Some responses such as 204s do not have content.  We must check.  
  2.                 if (httpResponse.getEntity() != null) {//返回响应主体  
  3.                   responseContents = entityToBytes(httpResponse.getEntity());//将主体转换byte[]形式  
  4.                 } else {//没有返回内容  
  5.                   // Add 0 byte response as a way of honestly representing a  
  6.                   // no-content request.  
  7.                   responseContents = new byte[0];  
  8.                 }  

其实就是没有内容的时候,也建造一个byte[0]数组

 

最后,凡是http头有以上错误的(304除外),抛出异常

 

[java] view plain copy
 
  1. if (statusCode < 200 || statusCode > 299) {//网络错误  
  2.                     throw new IOException();  
  3.                 }  

 

 

是不是抛出异常就结束了呢?当然不是,因为volley还要根据request的RetryPolicy来进行重试,下面我们来看catch段的代码

 

[java] view plain copy
 
  1. catch (SocketTimeoutException e) {  
  2.                 attemptRetryOnException("socket", request, new TimeoutError());  
  3.             } catch (ConnectTimeoutException e) {  
  4.                 attemptRetryOnException("connection", request, new TimeoutError());  
  5.             } catch (MalformedURLException e) {  
  6.                 throw new RuntimeException("Bad URL " + request.getUrl(), e);  
  7.             }   


可以看到,连接超时,地址格式错误,这些错误都会调用attemptRetryOnException()方法来进行重试

 

 

[java] view plain copy
 
  1. /** 
  2.     * Attempts to prepare the request for a retry. If there are no more attempts remaining in the 
  3.     * request‘s retry policy, a timeout exception is thrown. 
  4.     * @param request The request to use. 
  5.     * 准备重试请求.如果重试策略里面,没有新的目标,抛出异常 
  6.     */  
  7.    private static void attemptRetryOnException(String logPrefix, Request<?> request,  
  8.            VolleyError exception) throws VolleyError {  
  9.        RetryPolicy retryPolicy = request.getRetryPolicy();//重试策略  
  10.        int oldTimeout = request.getTimeoutMs();  
  11.   
  12.        try {  
  13.            retryPolicy.retry(exception);//重试  
  14.        } catch (VolleyError e) {  
  15.            request.addMarker(  
  16.                    String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));  
  17.            throw e;  
  18.        }  
  19.        request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));  以上是关于volley介绍06的主要内容,如果未能解决你的问题,请参考以下文章

    volley介绍08

    volley介绍03

    使用 GSON 和 Volley 在 Android 上显示 JSON 数据

    Volley异常的介绍

    Android实战--英文词典(API+GSON+Volley)

    Android实战--英文词典(API+GSON+Volley)