Volley 框架解析(二)--RequestQueue核心解读
主要围绕RequestQueue进行解读,它的两个请求队列CacheQueue、NetworkQueue是如何调用的,第一条请求的执行过程及如何处理重复请求?对RequestQueue及相关的类进行详细解读。
1.RequestQueue:
Volley 框架的核心类,将请求 Request 加入到一个运行的RequestQueue中,来完成请求操作。
RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、NetworkDispatcher数组(用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口),通过 start() 函数启动时会启动CacheDispatcher和NetworkDispatchers。
CacheDispatcher:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。
NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。
ResponseDelivery:返回结果分发接口,目前只有基于ExecutorDelivery的在入参 handler 对应线程内进行分发。
HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack。
Network:调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse。
Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCache。NetworkDispatcher得到请求结果后判断是否需要存储在 Cache,CacheDispatcher会从 Cache 中取缓存结果。
(1). 主要成员变量
RequestQueue 中维护了两个基于优先级的 Request 队列,缓存请求队列和网络请求队列。
放在缓存请求队列中的 Request,将通过缓存获取数据;放在网络请求队列中的 Request,将通过网络获取数据。
mCacheQueue缓存请求队列,mNetworkQueue发起网络请求的队列。
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();
维护了一个正在进行中,尚未完成的请求集合。//add方法会把请求先加入到mCurrentRequests中,也就是说mCurrentRequests包含了当前所有的请求,Request 请求结束时,首先从正在进行中请求集合mCurrentRequests中移除该请求。
this.mCurrentRequests = new HashSet();
维护了一个等待请求的集合,如果一个请求正在被处理并且可以被缓存,后续的相同 url 的请求,将进入此等待队列。Request 请求结束(finish), 然后查找请求等待集合mWaitingRequests中是否存在等待的请求,如果存在,则将等待队列移除,并将等待队列所有的请求添加到缓存请求队列中mCacheQueue,让缓存请求处理线程CacheDispatcher自动处理。
this.mWaitingRequests = new HashMap();
构造方法: threadPoolSize//默认是4,
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
(2). 启动队列
创建出 RequestQueue 以后,调用 start 方法,启动队列。一个cache调度线程,4个network调度线程,加上主线程共六个线程。
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
//mCache是硬盘缓存策略
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
3)add加入请求
public <T> Request<T> add(Request<T> request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this); //把requestQueue绑定到request中
synchronized (mCurrentRequests) {
mCurrentRequests.add(request); //加入当前请求集合
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());//获得唯一序列号
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {//是否可以被缓存
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();//就是他的url
if (mWaitingRequests.containsKey(cacheKey)) { //是否包含
// There is already a request in flight. Queue up.
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request); //加入到cachequeue中
}
return request;
}
}
4). 请求完成
void finish(Request<?> request)
<T> void finish(Request<T> request) {
// Remove from the set of requests currently being processed.
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
synchronized (mFinishedListeners) { //这个接口是在requestQueue中写的
for (RequestFinishedListener<T> listener : mFinishedListeners) {
listener.onRequestFinished(request);
}
} if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null) {
if (VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
// Process all queued up requests. They won't be considered as in flight, but
// that's not a problem as the cache has been primed by 'request'.
mCacheQueue.addAll(waitingRequests);
}
}
}
}
Request 请求结束
(1). 首先从正在进行中请求集合
mCurrentRequests中移除该请求。
(2). 然后查找请求等待集合mWaitingRequests中是否存在等待的请求,如果存在,则将等待队列移除,并将等待队列所有的请求添加到缓存请求队列中,让缓存请求处理线程CacheDispatcher自动处理。
(5). 请求取消
public void cancelAll(RequestFilter filter)
public void cancelAll(final Object tag)
取消当前请求集合中所有符合条件的请求。
filter 参数表示可以按照自定义的过滤器过滤需要取消的请求。
tag 表示按照Request.setTag设置好的 tag 取消请求,比如同属于某个 Activity 的。
3.CacheDispatcher
一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery 去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。
(1). 成员变量
BlockingQueue<Request<?>> mCacheQueue 缓存请求队列BlockingQueue<Request<?>> mNetworkQueue 网络请求队列Cache mCache 缓存类,代表了一个可以获取请求结果,存储请求结果的缓存ResponseDelivery mDelivery 请求结果传递类
run代码://无限遍历mCacheQueue,取出request,从cache中获取相应结果,如果结果为空或者过期,则发起网络请求。否则就将解析结果返回。
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();
while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
}
(2). 处理流程图
**在具体的请求子类中解析结果,用ExecutorDelivery(new Handler(MainLooper))传递结果,代码如下:
//以StringRequest为例,就是把结果按照他的编码格式存到response中。
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
ExecutorDelivery的postResponse(request,response):
public void postError(Request<?> request, VolleyError error) {
request.addMarker("post-error");
Response<?> response = Response.error(error);
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
}
//用主线程的handler发送任务到主线程的messageQueue中。
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
4.NetworkDispatcher.java
一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给 ResponseDelivery 去执行后续处理,并判断结果是否要进行缓存。
(1). 成员变量
BlockingQueue<Request<?>> mQueue 网络请求队列Network mNetwork 网络类,代表了一个可以执行请求的网络Cache mCache 缓存类,代表了一个可以获取请求结果,存储请求结果的缓存ResponseDelivery mDelivery 请求结果传递类,可以传递请求的结果或者错误到调用者
run代码:
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// Take a request from the queue.
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {//notModified:HTTP 304,参考https://www.douban.com/note/161120791/,
//就是服务器告诉客户端,文件没有更新,不用在请求啦。
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
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);
}
}
}
(2). 处理流程图
5. Cache.java
缓存接口,代表了一个可以获取请求结果,存储请求结果的缓存。
(1). 主要方法:
public Entry get(String key); 通过 key 获取请求的缓存实体public void put(String key, Entry entry); 存入一个请求的缓存实体public void remove(String key); 移除指定的缓存实体public void clear(); 清空缓存
(2). 代表缓存实体的内部类 Entry
成员变量和方法byte[] data 请求返回的数据(Body 实体)String etag Http 响应首部中用于缓存新鲜度验证的 ETaglong serverDate Http 响应首部中的响应产生时间long ttl 缓存的过期时间long softTtl 缓存的新鲜时间Map<String, String> responseHeaders 响应的 Headersboolean isExpired() 判断缓存是否过期,过期缓存不能继续使用boolean refreshNeeded() 判断缓存是否新鲜,不新鲜的缓存需要发到服务端做新鲜度的检测
public static class Entry {
/** The data returned from cache. */
public byte[] data;
/** ETag for cache coherency. Http 响应首部中用于缓存新鲜度验证的 ETag*/
public String etag;
/** Date of this response as reported by the server. */
public long serverDate;
/** The last modified date for the requested object. */
public long lastModified;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
/** Immutable response headers as received from server; must be non-null. */
public Map<String, String> responseHeaders = Collections.emptyMap();
/** True if the entry is expired. */
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/** True if a refresh is needed from the original data source. */
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
6.第一条请求的执行过程及如何处理重复请求
6.1成员变量:
维护了一个正在进行中,尚未完成的请求集合。HashSet实现的不可重复,所以这个集合里是正在进行中,尚未完成的请求,并且没有重复!Request 请求结束(finish),移出请求。
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
一个等待请求的集合,如果一个请求正在被处理并且可以被缓存,后续的相同 url 的请求,将进入此等待队列。Request 请求结束(finish), 然后查找请求等待集合mWaitingRequests中是否存在等待的请求,如果存在,则将等待队列移除,并将等待队列所有的请求添加到缓存请求队列中mCacheQueue,让缓存请求处理线程CacheDispatcher自动处理。
HashMap实现key不可重复,queue是linkedList实现的。
private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>();
mCacheQueue缓存请求队列,CacheDispatcher无限遍历这个队列。
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
mNetworkQueue网络请求队列,发起网络请求得队列。NetworkDispatcher无限循环这个队列,取出request访问网络。
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
6.2第一条请求的处理过程
1)在RequestQueue的add方法中,先把请求加入mCurrentRequests(hashSet实现的,元素不可重复,后续相同的加不了)。
2)判断请求是否允许缓存,不允许就是加入mNetworkQueue,允许执行第三步。
3)mWaitingRequests中是否包含此请求,如果包含就加入到相同请求的等待队列中。如果不包含,说明之前没有请求过。就加入到mWaitingRequests和mCacheQueue中mWaitingRequests.put(cacheKey,null),mCacheQueue.add(request);这里主要看mCacheQueue,这时候CacheDispatcher就会把request取出来,判断是否在缓存cache,这是第一个,肯定不在,就加入mNetWorkQueue,NetworkDispatcher就会把它取出来,执行网络请求。
4)请求完成时,通过ResponseDelivery传送Response给主线程,request调用finish方法结束请求, 首先从正在进行中请求集合mCurrentRequests中移除该请求。
然后查找请求等待集合mWaitingRequests中是否存在等待的请求,如果存在,则将等待队列移除,并将等待队列所有的请求添加到缓存请求队列mCacheQueue中,让缓存请求处 理线程CacheDispatcher自动处理。
//在NetworkDispatcher中,传送
mDelivery.postResponse(request, response);
6.3有重复请求时,但是该请求正在访问网络
1)add方法加入,因为mCurrentRequests中已经包含,不能加入到mCurrentRequests。
2)判断请求是否允许缓存,不允许就是加入mNetworkQueue,允许执行第三步。
3)检查mWaitingRequests中是否包含此请求,因为之前已经有啦,就加入到相同请求的等待队列中(LinkedList实现的队列)。等待访问网络的请求完成。
4)然后就是6.2的第四步
6.4有重复请求,已经在mCache中
这个最简单,第一个重复的请求加入mCacheQueue ,由CacheDispatcher处理,在Cache取出相应结果,传给主线程。request调用finish方法
1)在RequestQueue的add方法中,先把请求加入mCurrentRequests(hashSet实现的,元素不可重复,后续相同的加不了)。
2)判断请求是否允许缓存,不允许就是加入mNetworkQueue,允许执行第三步。
3)mWaitingRequests中是否包含此请求,如果包含就加入到相同请求的等待队列中。如果不包含,说明之前没有请求过。就加入到mWaitingRequests和mCacheQueue中 mWaitingRequests.put(cacheKey,null),mCacheQueue.add(request);这里主要看mCacheQueue,这时候CacheDispatcher就会把request取出来,判断是否在缓存cache,
找到缓存结果用ExecutorDelivery传递给主线程,request调用finish·方法。
mDelivery.postResponse(request, response); //还是这个方法
转发请注明出处:http://www.cnblogs.com/jycboy/p/volley-requestqueue.html
Volley 框架解析(二)--RequestQueue核心解读的更多相关文章
- Android Volley完全解析(二),使用Volley加载网络图片
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17482165 在上一篇文章中,我们了解了Volley到底是什么,以及它的基本用法. ...
- Disruptor并发框架 (二)核心概念场景分析
核心术语 RingBuffer(容器): 被看作Disruptor最主要的组件,然而从3.0开始RingBuffer仅仅负责存储和更新在Disruptor中流通的数据.对一些特殊的使用场景能够被用户( ...
- [转] Android Volley完全解析(一),初识Volley的基本用法
版权声明:本文出自郭霖的博客,转载必须注明出处. 目录(?)[-] Volley简介 下载Volley StringRequest的用法 JsonRequest的用法 转载请注明出处:http ...
- Android Volley完全解析
1. Volley简介 我们平时在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据.Android系统中主要提供了两种方式来进行H ...
- Android Volley完全解析(三),定制自己的Request
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17612763 经过前面两篇文章的学习,我们已经掌握了Volley各种Request ...
- 【安卓网络请求开源框架Volley源码解析系列】定制自己的Request请求及Volley框架源码剖析
通过前面的学习我们已经掌握了Volley的基本用法,没看过的建议大家先去阅读我的博文[安卓网络请求开源框架Volley源码解析系列]初识Volley及其基本用法.如StringRequest用来请求一 ...
- Android 学习笔记之Volley开源框架解析(一)
PS:看完了LGD的六场比赛...让人心酸... 学习内容: 1.Http请求的过程... 2.Volley的简单介绍... 1.Http请求... 这里只是简单的说一下Http请求的过程.. ...
- Android Volley框架的使用(二)
此博文源码下载地址 https://github.com/Javen205/VolleyDemo.git 使用请求队列RequestQueue Volley中的Request都需要添加到Reque ...
- Android网络通信Volley框架源代码浅析(二)
尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列: ...
随机推荐
- poj 1751 输出MST中新加入的边
给出结点的坐标 以及已建好的边 要输出MST中加入的边(已建好的边就不用输出了)结点的编号从1开始注意这题只有一组数据 不能用多组输入 否则就超时(在这被坑惨了Orz) Sample Input 91 ...
- 彻底解决:java.sql.SQLException: Incorrect string value: '\xF0\x9F\x92\x94' for column 'name' at row 1
转载:https://blog.csdn.net/qq_31122833/article/details/83992085
- 白化(Whitening): PCA 与 ZCA (转)
转自:findbill 本文讨论白化(Whitening),以及白化与 PCA(Principal Component Analysis) 和 ZCA(Zero-phase Component Ana ...
- 【BZOJ4504】K个串
题解: 这题跟超级noi钢琴思路大致相同 不同之处在于如何寻找最大值 这道题里出现了每个数都只能被算一次这个限制 我们考虑一下如果还要使用主席树和前缀和该怎么做 我们每次操作一个数时,可以让这个数上一 ...
- 016 在大数据中,SSH无密钥登录
一:概述 1.关于ssh ssh是一种安全协议. 会生成一对公钥和私钥. 2.问题的由来 3.解决方式 将生成的公钥发送到远程的机器上. 4.位置 主目录下的.ssh文件下. 二:在伪分布式下的操作 ...
- pygame游戏开发入门例子
# *_* coding:utf-8 *_* # 开发团队:中国软件开发团队# 开发人员:Administrator# 开发时间:2019/3/23 11:16# 文件名称:pygame_demo# ...
- Linux学习之文件属性chattr权限与sudo权限(十二)
Linux学习之文件属性chattr权限与sudo权限 文件属性chattr Linux文件的隐藏属性在保护系统文件的安全性上非常重要,是防止误操作的,对root用户也同样有效.chattr命令只能在 ...
- Excel获取第一个表名
Excel.Worksheet wsheet1 = (Excel.Worksheet)excelSql.Worksheets.get_Item(); wsheet1.Name获取sheet名称
- Java并发程序设计(七)乐天派:无锁
无锁 一.概述 无锁是处理并发的一种乐观策略,它会假设对资源的访问是没有冲突的.既然没有冲突自然不需要等待,所以所有的线程都可以在不停顿的状态下执行.那遇到冲突怎么办?接下来请看,无锁绝招“CAS”即 ...
- 在web中实现当前变量和前一个的比较
/*if (attribute == null) {// 如果缓存中没有存过UploadTimeObj对象,那么创建对象,并将该对象存入缓存中 UploadTim ...