Volley源码分析(二)CacheDispatcher分析
CacheDispatcher 缓存分发
cacheQueue只是一个优先队列,我们在start方法中,分析了CacheDispatcher的构成是需要cacheQueue,然后调用CacheDispatcher.start方法,我们看一下CacheDispatcher得到cacheQueue之后,到底做了什么。
CacheQueue是一个继承于Thread的类,其start方法实质上是调用了run方法,我们看一下run方法所做的事情
@Override
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;
}
}
}
}
我们可以看出其Run方法是一个无限循环的方法,退出的方式只有产生中断异常,也就是其thread对象调用了 interrupt()方法,这个方法是在requestQueue中的stop方法中调用了,上面我们已经分析了。
下面我们主要run方法的执行过程,取出队头的request,然后判断request是否被取消,如果没有就判断该request中取出entity,判断entity的状态,如果entity为空,则将该request放入NetWorkDispatcher中重新请求,如果entity过期了也将该request放入NetWorkDispatcher中重新请求。两者都没有,则从request中entity的内容,重新构造response,然后判断该entity是否需要刷新,不需要就直接Delivery该response,如果需要刷新,则将该response依旧发给用户,但是重新进行请求该刷新entity。
可以用下图的逻辑去看上面的过程

这里,涉及到了Http一个重要的点,缓存。我们看一下,entity中关于缓存是怎么设置的.
class Entry {
/** The data returned from cache. */
public byte[] data;
/** ETag for cache coherency. */
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. */
boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/** True if a refresh is needed from the original data source. */
boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
这里的过期方法判断与是否需要刷新都是通过TTL与softTTL和现在时间对比而得到的。
缓存
这里说一下Volley的缓存机制,涉及到Http缓存,需要解析Http响应报文的头部。
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
long now = System.currentTimeMillis();
Map<String, String> headers = response.headers;
long serverDate = 0;
long lastModified = 0;
long serverExpires = 0;
long softExpire = 0;
long finalExpire = 0;
long maxAge = 0;
long staleWhileRevalidate = 0;
boolean hasCacheControl = false;
boolean mustRevalidate = false;
String serverEtag;
String headerValue;
headerValue = headers.get("Date");
if (headerValue != null) {
serverDate = parseDateAsEpoch(headerValue);
}
// 获取响应体的Cache缓存策略.
headerValue = headers.get("Cache-Control");
if (headerValue != null) {
hasCacheControl = true;
String[] tokens = headerValue.split(",");
for (String token : tokens) {
token = token.trim();
if (token.equals("no-cache") || token.equals("no-store")) {
// no-cache|no-store代表服务器禁止客户端缓存,每次需要重新发送HTTP请求
return null;
} else if (token.startsWith("max-age=")) {
// 获取缓存的有效时间
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
maxAge = 0;
}
} else if (token.startsWith("stale-while-revalidate=")) {
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e) {
staleWhileRevalidate = 0;
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
// 需要进行新鲜度验证
mustRevalidate = true;
}
}
}
// 获取服务器资源的过期时间
headerValue = headers.get("Expires");
if (headerValue != null) {
serverExpires = parseDateAsEpoch(headerValue);
}
// 获取服务器资源最后一次的修改时间
headerValue = headers.get("Last-Modified");
if (headerValue != null) {
lastModified = parseDateAsEpoch(headerValue);
}
// 获取服务器资源标识
serverEtag = headers.get("ETag");
// 计算缓存的ttl和softTtl
if (hasCacheControl) {
softExpire = now + maxAge * 1000;
finalExpire = mustRevalidate
? softExpire
: softExpire + staleWhileRevalidate * 1000;
} else if (serverDate > 0 && serverExpires >= serverDate) {
// Default semantic for Expire header in HTTP specification is softExpire.
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.etag = serverEtag;
entry.softTtl = softExpire;
entry.ttl = finalExpire;
entry.serverDate = serverDate;
entry.lastModified = lastModified;
entry.responseHeaders = headers;
return entry;
}
这里设计到缓存,就要先得到Http的cache-control的headervalue,如果是no-cahce||no-store就不需要再处理缓存,虽然on-cache在浏览器那边还是保存了请求的资源,但这里去没有处理。如果headervalue中有MaxAge,这个值是判断缓存存在的有效时间。如果headervalue中有stale-while-revalidate,这个值是缓存过期的可用时间,即使缓存过期,在stale-while-revalidate时间内依旧可用。如果headervalue中有must-revalidate就意味着
从必须再验证缓存的新鲜度,然后再用。
然后继续解析header与缓存有关的内容,如Expires(这是一个不推荐的标签),Last-Modified(最近被修改的时间),ETag(服务器资源标识)。
然后如果有缓存控制就计算缓存的TTL与SoftTTL,SoftTTL就是softExpire,其值就是maxAge + 当前时间,而TTL是finalTTL其值是 先判断是否过期就再验证,如果是的话,其值就是softExpire,如果不是的话,其值就是softExpire加上staleWhileRevalidate(缓存过期有效时间)。
如果没有缓存控制,softExpire = now + (serverExpires - serverDate);
所以,说回上面,CacheQueue中缓存的判断,isExpire就是判断finalTTL是否超过当前时间,而refreshNeeded则是判断softExpire是否超过当前时间。
Volley源码分析(二)CacheDispatcher分析的更多相关文章
- # Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析#
Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析 Volley源码一共40多个类和接口.除去一些工具类的实现,核心代码只有20多个类.所以相对来说分析起来没有那么吃力.但是要想分析透 ...
- Volley源码分析(2)----ImageLoader
一:imageLoader 先来看看如何使用imageloader: public void showImg(View view){ ImageView imageView = (ImageView) ...
- Android Volley源码分析
今天来顺手分析一下谷歌的volley http通信框架.首先从github上 下载volley的源码, 然后新建你自己的工程以后 选择import module 然后选择volley. 最后还需要更改 ...
- Volley源码分析一
Volley源码分析 虽然在2017年,volley已经是一个逐渐被淘汰的框架,但其代码短小精悍,网络架构设计巧妙,还是有很多值得学习的地方. 第一篇文章,分析了请求队列的代码,请求队列也是我们使用V ...
- Volley源码分析(一)RequestQueue分析
Volley源码分析 虽然在2017年,volley已经是一个逐渐被淘汰的框架,但其代码短小精悍,网络架构设计巧妙,还是有很多值得学习的地方. 第一篇文章,分析了请求队列的代码,请求队列也是我们使用V ...
- Volley 源码分析
Volley 源码分析 图片分析 要说源码分析,我们得先看一下官方的配图: 从这张图中我们可以了解到 volley 工作流程: 1.请求加入优先队列 2.从缓存调度器中查看是否存在该请求,如果有(没有 ...
- Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析
Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析 Volley之所以高效好用,一个在于请求重试策略,一个就在于请求结果缓存. 通过上一篇文章http://www.cnblogs.com ...
- HashMap的源码学习以及性能分析
HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...
- 物联网防火墙himqtt源码之MQTT协议分析
物联网防火墙himqtt源码之MQTT协议分析 himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWall,C语言编写,采用epoll模式支持数十万 ...
随机推荐
- jQuery获取动态产生的html内标签或元素
下午分享<MVC编辑状态两个DropDownList联动>http://www.cnblogs.com/insus/p/3426563.html 不久,马上有网友问及三级联动的例子.Ins ...
- REST风格的应用程序实现
莫笑我老土,因为我确实是最近才听说REST风格的,以前就是觉得 /category/product/pid 这样的地址非常的漂亮,但是那只是表象罢了,了解深入以后,发现必须有一个客户端的Ajax En ...
- Python发送短信提醒
Python发送短信可借助腾讯云平台提供的短信服务 发送短信需要的及格参数: 1.SDK_AppID和SDK_Key 2.签名: 3.模板ID 下面贴出源码DEMO: from qcloudsms_p ...
- SpringBoot,Vue前后端分离开发首秀
需求:读取数据库的数据展现到前端页面 技术栈:后端有主要有SpringBoot,lombok,SpringData JPA,Swagger,跨域,前端有Vue和axios 不了解这些技术的可以去入门一 ...
- The area (hdu1071)积分求面积
The area Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submi ...
- java如何正确停止一个线程
Thread类中有start(), stop()方法,不过stop方法已经被废弃掉. 平时其实也有用过,共享一个变量,相当于标志,不断检查标志,判断是否退出线程 如果有阻塞,需要使用Thread的in ...
- 3.Decorator Pattern(装饰者模式)
装饰者模式: 动态地将责任附加到对象上.想要扩展功能,装饰者提供有别于继承的另一种选择. 举例: 不知道大家学校的食堂是什么点餐制度(或者大家就直接想成吃火锅,我们要火锅料 + 配菜),我们学校的点餐 ...
- python3爬虫(find_all用法等)
#read1.html文件 # <html><head><title>The Dormouse's story</title></head> ...
- CentOS7上搭建LDAP-PDC并且将windows 2008 R2加入LDAP-PDC域
由于测试原因,要涉及到将windows机器加入到ldap域,所以查看各种文档进行ldap-pdc域的搭建,并成功将windows 2008r2加入到ldap-pdc域中.下面简单记录一下搭建过程 Li ...
- Windows7安装 nginx+php 后访问.php文件出现 “No input file specified.” 的解决办法
在Windows7上安装了Nginx+PHP,参考教程为 https://www.cnblogs.com/anlia/p/5916758.html 启动 nginx 后,在浏览器中输入localhos ...