在阅读之前
本文章默认你已经会使用volley的前提下所总结的, 如果你还未清楚volley的使用, 建议你阅读前面关于volley介绍的文章, 阅读文章之后如果你有任何问题, 欢迎留言交流.
开始吧
- 首先, 从我们new一个请求队列开始, volley的底层就已经默认开启了5个线程在循环工作等待请求队列的添加, 所以, 我们在使用时必须保证请求队列的全局唯一性, 避免资源的浪费.
- 从请求队列的构造函数我们可以看出, 构造函数的参数分别有缓存接口Cache; 执行网络请求的接口Network; 网络请求线程池数量threadPoolSize, 默认是4; 还有一个请求结果和错误的分发器ResponseDelivery, 它也是一个接口, 从它接收一个通过Handler绑定主线程的ExecutorDelivery对象可以看出, 它的作用是将网络请求的结果和错误通过Handler交付给主线程处理.
所有的参数都通过成员变量保存起来.
从整个构造函数的参数可以看出, 谷歌很充分的利用了面向接口编程, 很大程度上体现了volley具有很强的扩展性. - 从缓存接口接收的DiskBasedCache开始, 我们需要创建磁盘缓存, 缓存目录是Cache目录下的volley文件夹, 默认大小是5M.
- 第二个参数network, 接收一个封装了HttpStack的BasicNetwork对象, 我们来看一下HttpStack接口的实现关系: HttpClientStack和HurlStack实现了这个接口.
- HttpClientStack内部其实就是通过第三方网络请求工具Apache的HttpClient实现了网络请求, 而HurlStack内部是通过HttpURLConnection发起网络请求的.
- 在volley类的源码里, 我们可以看到判断如果当前手机系统SDK版本在9之后, 创建HurlStack网络请求, 而在9之前, 则是创建HttpClientStack作为网络请求工具.
- 谷歌对此也给了解析: 在版本9之前, HttpUrlConnection was unreliable. 也就是说, 使用volley时, 我们不需要关系网络请求是如何实现的, volley已经封装好了最好的选择方案, 保证了volley的稳定性.
- 所有的参数准备好之后, 才是真正的开始创建了一个RequestQueue, 调用它的start方法.
- 在start方法里, 我们可以看到new了一个CacheDispatcher缓存分发器和利用for循环默认new了4个NetworkDispatcher网络请求的分发器, CacheDispatcher和NetworkDispatcher继承了Thread类, 说明它们是一个线程.
- CacheDispatcher创建时传递了4个参数过去, 分别是一个在成员变量声明好的具有优先级的缓存请求队列CacheQueue,一个同样具有优先级的网络请求队列NetworkQueue, 一个之前创建好的磁盘缓存DiskBasedCache和一个ExecutorDelivery.
- 同样的NetworkDispatcher在创建时传递过去4个参数, 分别是一个网络请求队列, 一个网络请求, 一个磁盘缓存和一个ExecutorDelivery.
现在开始发送网络请求了
CacheDispatcher的run方法中, 首先就将DiskBasedCache进行初始化, 调用它的initialize方法判断当前缓存目录是否存在, 如果不存在, 则创建缓存目录然后return,如果存在缓存目录, 遍历所有的缓存文件, 读出所有缓存文件的头信息CacheHeader(包含key和过期时间等), 存入头的集合LinkedHashMap.
private void putEntry(String key, CacheHeader entry) { if (!mEntries.containsKey(key)) { mTotalSize += entry.size; } else { CacheHeader oldEntry = mEntries.get(key); mTotalSize += (entry.size - oldEntry.size); } mEntries.put(key, entry); }
当发生网络请求时, 从集合中获取key, 这个key是请求类型+url组成的字符串, 通过key来获取缓存文件头信息保存到Cache.Entry里.
- 网络请求首先默认添加到mCacheQueue, CacheDispatcher会一直循环的查询mCacheQueue里有没有请求.
如果拿到了网络请求,便查询有没有缓存
Cache.Entry entry = mCache.get(request.getCacheKey());
如果没有缓存, 或者缓存已过期, 即”cache-miss”和”cache-hit-expired”, 直接将请求添加到网络请求队列中
mNetworkQueue.put(request);
如果entry不为null,并且缓存没有过期,即”cache-hit”, 就解析缓存网络请求的响应
Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); 这个parseNetworkResponse方法的实现是交给Request的子类来完成的,因为不同种类的Request解析的方式也肯定不同。 自定义Request中,parseNetworkResponse()这个方法就是必须要重写的.
分发解析后的结果到主线程
mDelivery.postResponse(request, response); 因为mDelivery是绑定到主线程的, 所以这个方法是如何在主线程运行的? public void postResponse(Request<?> request, Response<?> response, Runnable runnable) { request.markDelivered(); request.addMarker("post-response"); //传入了一个ResponseDeliveryRunnable对象,就可以保证该对象中的run()方法就是在主线程当中运行的了 mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); } 再看看ResponseDeliveryRunnable的run方法, // Deliver a normal response or error, depending. if (mResponse.isSuccess()) { mRequest.deliverResponse(mResponse.result); 这个就是我们在自定义Request时需要重写的另外一个方法,每一条网络请求的响应都是回调到这个方法中. } else { mRequest.deliverError(mResponse.error); }
加入到mNetworkQueue后
NetworkDispatcher里面也会一直循环的查询mNetworkQueue有没有请求
如果mNetworkQueue如果有请求,则执行网络请求NetworkResponse networkResponse = mNetwork.performRequest(request);
解析网络请求结果
Response<?> response = request.parseNetworkResponse(networkResponse);
将网络请求的结果存入缓存
mCache.put(request.getCacheKey(), response.cacheEntry);
将解析后的结果分发给主线程
mDelivery.postResponse(request, response);