20170908工作日记--Volley源码详解
Volley没有jar包,需要从官网上下载源码自己编译出来,或者做成相关moudle引入项目中。我们先从最简单的使用方法入手进行分析:
1 //创建一个网络请求队列 2 RequestQueue requestQueue = Volley.newRequestQueue(this); 3 String url = "http://news-at.zhihu.com/api/4/news/latest"; 4 //创建一个网络请求 5 StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { 6 @Override 7 public void onResponse(String response) { 8 Log.e("SourceAnalysis", response); 9 } 10 }, new Response.ErrorListener() { 11 @Override 12 public void onErrorResponse(VolleyError error) { 13 Log.e("SourceAnalysis", " error:" + error.toString()); 14 } 15 }); 16 //网络请求添加到请求队列 17 requestQueue.add(request);
具体使用就下面三步:
1. 创建一个RequestQueue对象。
2. 创建一个StringRequest对象。
3. 将StringRequest对象添加到RequestQueue里面。
首先从第一句开始利用Volley这个类创建了一个请求队列,Volley这个类也非常简单,只有两个构造函数,返回一个创建好的请求队列。
- 创建一个默认的缓冲目录,在User-Agent中存入包名和应用版本信息
- 这里有一个HttpStack的接口,下面有两个实现类分别是HurlStack和HttpClientStack,后面我们会具体分析整两个类
- 然后将上面的Stack作为参数传入网络工作线程中
- 利用RequestQueue的构造函数,创造出一个请求队列,并返回,其中还有一个重要方法start(),启动线程。
Volley类的构造函数就是我们接下来进行分析的主要枝干:
1 public static RequestQueue newRequestQueue(Context context, HttpStack stack) { 2 File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); 3 4 String userAgent = "volley/0"; 5 try { 6 String packageName = context.getPackageName(); 7 PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); 8 userAgent = packageName + "/" + info.versionCode; 9 } catch (NameNotFoundException e) { 10 } 11 12 if (stack == null) { 13 if (Build.VERSION.SDK_INT >= 9) { 14 stack = new HurlStack(); 15 } else { 16 // Prior to Gingerbread, HttpUrlConnection was unreliable. 17 // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html 18 stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); 19 } 20 } 21 22 Network network = new BasicNetwork(stack); 23 24 RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); 25 queue.start(); 26 27 return queue; 28 }
接下来我们按照构造方法中的创建顺序,对源码进行解析。首先对Stack是否为空进行了判断,如果版本号大于9则创建HurlStack的实例,如果小于9则创建HttpClientStack的实例。分别对接口HttpStack、HurlStack、HttpClientStack进行分析。
HttpStack类图:
HttpStack 是一个接口并且只有一个 performRequest 方法,而 HurlStack 和 HttpClientStack 分别是基于 HttpUrlConnection 和 HttpClient 对HttpStack 的实现,是真正用来访问网络的类。
首先学习下 HurlStack类的内部方法:
1 public HurlStack() { 2 this(null); 3 } 4 5 /** 6 * @param urlRewriter Rewriter to use for request URLs 7 */ 8 public HurlStack(UrlRewriter urlRewriter) { 9 this(urlRewriter, null); 10 } 11 12 /** 13 * @param urlRewriter Rewriter to use for request URLs 14 * @param sslSocketFactory SSL factory to use for HTTPS connections 15 */ 16 public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) { 17 mUrlRewriter = urlRewriter; 18 mSslSocketFactory = sslSocketFactory; 19 }
可以看到,这里 Volley 默认调用的是最上面那个构造器,但其实最上面的构造函数会调用有一个参数的也就是第二个构造函数,并将参数值设为 null,同样第二个会调用最后一个有两个参数的构造函数,并将参数设为 null。也就是将 urlRewriter 和 sslSocketFactory 都初始化为 null。
当然我们如果有拦截 URL 需求或者安全需求需要用到 HTTPS 的话,可以自己写 HttpStack 实现需求,然后传给 Volley 的 newRequestQueue 方法。 Volley 基于接口编程。
最后再来看核心方法 performRequest,其余的方法都是为它服务的,注释写的比较清晰了:
1 /** 2 * 需要实现父类的接口方法,发送网络请求 3 * 4 * @param request the request to perform 将要执行的请求 5 * @param additionalHeaders additional headers to be sent together with 6 * {@link Request#getHeaders()} 将附加的头信息添加到获取到的头信息中 7 * @return 8 * @throws IOException 9 * @throws AuthFailureError 10 */ 11 @Override 12 public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) 13 throws IOException, AuthFailureError { 14 //从请求消息中获取需要访问的URL地址 15 String url = request.getUrl(); 16 //创建一个map存放请求消息头和附加的消息头 17 HashMap<String, String> map = new HashMap<String, String>(); 18 map.putAll(request.getHeaders()); 19 map.putAll(additionalHeaders); 20 //看个人的需求,如果需要重新构造请求的URL,则在此重写URL地址并返回 21 if (mUrlRewriter != null) { 22 String rewritten = mUrlRewriter.rewriteUrl(url); 23 if (rewritten == null) { 24 throw new IOException("URL blocked by rewriter: " + url); 25 } 26 url = rewritten; 27 } 28 //根据上面的URL构建网络连接 29 URL parsedUrl = new URL(url); 30 HttpURLConnection connection = openConnection(parsedUrl, request);//创建连接、SSL 31 for (String headerName : map.keySet()) {//将刚刚获取到的键值存放到connection中 32 connection.addRequestProperty(headerName, map.get(headerName)); 33 } 34 setConnectionParametersForRequest(connection, request);//设置返回数据的请求方式 35 // Initialize HttpResponse with data from the HttpURLConnection. 构建HTTP协议 36 ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); 37 int responseCode = connection.getResponseCode();//获取响应返回码 38 if (responseCode == -1) { 39 // -1 is returned by getResponseCode() if the response code could not be retrieved. 40 // Signal to the caller that something was wrong with the connection. 41 throw new IOException("Could not retrieve response code from HttpUrlConnection."); 42 } 43 //构建响应消息头,并返回响应消息头的信息 44 StatusLine responseStatus = new BasicStatusLine(protocolVersion, 45 connection.getResponseCode(), connection.getResponseMessage()); 46 BasicHttpResponse response = new BasicHttpResponse(responseStatus); 47 if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) { 48 response.setEntity(entityFromConnection(connection)); 49 } 50 for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { 51 if (header.getKey() != null) { 52 Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); 53 response.addHeader(h); 54 } 55 } 56 return response; 57 }
最终返回的是响应消息头。下面是这个方法的流程图:
至此,HttpStack这个接口以及实现类已经分析完了,这个类实现了从请求消息中设置网络访问。
现在我们回到Volley主枝干中,下面就是利用stack进行了BasicNetwork的初始化工作。下面我们将要分析NetWork接口和它的实现类BasicNetWork。他们的类图:
BasicNetwork的构造函数:
1 /** 2 * @param httpStack HTTP stack to be used 3 */ 4 public BasicNetwork(HttpStack httpStack) { 5 // If a pool isn't passed in, then build a small default pool that will give us a lot of 6 // benefit and not use too much memory. 7 //如果没有线程池传入,那么需要建立一个小容量的线程池,这将有利于我们的程序执行 8 this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE)); 9 } 10 11 /** 12 * @param httpStack HTTP stack to be used 13 * @param pool a buffer pool that improves GC performance in copy operations 14 */ 15 public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) { 16 mHttpStack = httpStack; 17 mPool = pool; 18 }
在Volley类创建Network的实例时默认使用的是只有一个参数的构造函数,但是他会调用下面的两个参数的构造函数。
这类的成员变量:
1 protected static final boolean DEBUG = VolleyLog.DEBUG; 2 3 //最长请求时间 4 private static final int SLOW_REQUEST_THRESHOLD_MS = 3000; 5 6 //线程池的内存容量大小 7 private static final int DEFAULT_POOL_SIZE = 4096; 8 9 //真正执行网络请求的类 10 protected final HttpStack mHttpStack; 11 12 protected final ByteArrayPool mPool;
- 两个常量,分别表示最长请求时间和线程池大小
- 个HttpStack 接口,真正执行网络请求的类
- 二进制数组池,一个工具类
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 = Collections.emptyMap(); 8 try { 9 // Gather headers. 10 //收集请求消息头信息 11 Map<String, String> headers = new HashMap<String, String>(); 12 addCacheHeaders(headers, request.getCacheEntry()); 13 14 //调用HttpStack进行网络访问,获取返回响应值 15 httpResponse = mHttpStack.performRequest(request, headers); 16 //获取返回响应状态以及响应返回码 17 StatusLine statusLine = httpResponse.getStatusLine(); 18 int statusCode = statusLine.getStatusCode(); 19 //返回消息头信息 20 responseHeaders = convertHeaders(httpResponse.getAllHeaders()); 21 //SC_NOT_MODIFIED就是304请求响应 22 //对304响应请求的处理流程 23 if (statusCode == HttpStatus.SC_NOT_MODIFIED) { 24 Entry entry = request.getCacheEntry(); 25 if (entry == null) { 26 //如果是304请求,且没有改动,则返回304状态码,响应消息头,以及从请求到响应的时间 27 return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null, 28 responseHeaders, true, 29 SystemClock.elapsedRealtime() - requestStart); 30 } 31 32 // A HTTP 304 response does not have all header fields. We 33 // have to use the header fields from the cache entry plus 34 // the new ones from the response. 35 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 36 //304的返回消息头信息如果不为空,我们需要从缓存消息中获取完整的消息去填充完整响应消息头 37 //最后返回完整的响应消息头 38 entry.responseHeaders.putAll(responseHeaders); 39 return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data, 40 entry.responseHeaders, true, 41 SystemClock.elapsedRealtime() - requestStart); 42 } 43 44 // Some responses such as 204s do not have content. We must check. 45 //对204返回码的验证 46 if (httpResponse.getEntity() != null) { 47 responseContents = entityToBytes(httpResponse.getEntity()); //将HTTP中返回的内容转换成字节数组 48 } else { 49 // Add 0 byte response as a way of honestly representing a 50 // no-content request. 51 responseContents = new byte[0]; 52 } 53 54 // if the request is slow, log it. 55 //如果请求时间过长则产生日志进行记录 56 long requestLifetime = SystemClock.elapsedRealtime() - requestStart; 57 logSlowRequests(requestLifetime, request, responseContents, statusLine); 58 59 //其他请求返回码的处理,抛出IO异常 60 if (statusCode < 200 || statusCode > 299) { 61 throw new IOException(); 62 } 63 //返回响应消息头 64 return new NetworkResponse(statusCode, responseContents, responseHeaders, false, 65 SystemClock.elapsedRealtime() - requestStart); 66 } catch (SocketTimeoutException e) { 67 attemptRetryOnException("socket", request, new TimeoutError()); 68 } catch (ConnectTimeoutException e) { 69 attemptRetryOnException("connection", request, new TimeoutError()); 70 } catch (MalformedURLException e) { 71 throw new RuntimeException("Bad URL " + request.getUrl(), e); 72 } catch (IOException e) { 73 int statusCode; 74 if (httpResponse != null) { 75 statusCode = httpResponse.getStatusLine().getStatusCode(); 76 } else { 77 throw new NoConnectionError(e); 78 } 79 VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); 80 //对网络返回的数据进行验证,对错误的响应进行处理 81 NetworkResponse networkResponse; 82 if (responseContents != null) { 83 networkResponse = new NetworkResponse(statusCode, responseContents, 84 responseHeaders, false, SystemClock.elapsedRealtime() - requestStart); 85 if (statusCode == HttpStatus.SC_UNAUTHORIZED || //401以及403响应消息 86 statusCode == HttpStatus.SC_FORBIDDEN) { 87 attemptRetryOnException("auth", 88 request, new AuthFailureError(networkResponse)); 89 } else if (statusCode >= 400 && statusCode <= 499) { 90 // Don't retry other client errors. 91 //编号为400—499范围的状态码表用户的请求未完成并需要用户提供更多的信息来实现对所需资源的访问 92 throw new ClientError(networkResponse); 93 } else if (statusCode >= 500 && statusCode <= 599) { 94 //由于内部服务器错误造成的 95 if (request.shouldRetryServerErrors()) { 96 attemptRetryOnException("server", 97 request, new ServerError(networkResponse)); 98 } else { 99 throw new ServerError(networkResponse); 100 } 101 } else { 102 // 3xx? No reason to retry. 103 throw new ServerError(networkResponse); 104 } 105 } else { 106 attemptRetryOnException("network", request, new NetworkError()); 107 } 108 } 109 } 110 }
需要注意的一点是:
Map<String, String> responseHeaders = Collections.emptyMap();这个函数的使用。Collections接口中有这样三个方法
Collections.emptySet()
Collections.emptyList()
Collections.emptyMap()
会生成指定类型的空List Set Map,而且是不可变的,如进行add()操作会报java.lang.UnsupportedOperationException。
它的优点和好处是:
方法内部会返回static final成员,创建后相当于常量可重复引用
- 防止空指针出现,当你的代码需要一个集合而这个集合可能不存在,此时尽量使用空集合而不是null,因为集合一个常用
的操作就是遍历,你不知道你返回的结果在后续会不会被遍历。比如一个查询步骤返回一个集合,当返回一个空集合是就
可以用这类方法,还可以防止后续对这个空集合再做add操作,参考Effactive JAVA 43条:返回0长度的数组或者集合,而不是null
- 对于泛型集合无需指定其类型参数,
如Map<Foo, Comparable<? extends Bar>> fooBarMap = new HashMap<Foo, Comparable<? extends Bar>>();
只要Map<Foo, Comparable<? extends Bar>> fooBarMap = Collections.emptyMap();即可,起到简化代码作用
- 使用集合的一个好习惯就是使用 immutable collection,参考 http://stackoverflow.com/questions/214714/mutable-vs-immutable-objects/214718#214718
有些场景没有遇见过,还不太能够理解。
流程图如下:
接下来走到了Volley主干的构造消息队列的函数中,构造一个请求消息队列RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);传进去一个磁盘缓存的东西,因此在学习构建消息队列前我们先把这个磁盘缓存的东西搞明白。请看下篇博客。