自从15年8月使用Volley以来,感触很多,源码也过了几次,第一篇博客三级缓存 Volley /Lrucache/DiskLrucache + AndroidStudio导出jar包 + upload to github就是使用Volley结合LruCache和DiskLruCache构建的三级缓存。其实关于Volley底层的实现在以前的博客中或多或少都有提及,例如缓存,队列等等,这篇博客就对它进行全方面的总结

缓存

首先,我们从缓存开始入手,毕竟这个算它的一个特点,也是一个优势。
我的第一篇博客实现三级缓存就利用的Volley的网络层缓存,LruCache的内存缓存和DiskLruCache的磁盘缓存。

Volley是实现了网络层缓存和内存缓存的。

网络请求的发起流程

  1. 检测是否开启缓存,如果开启,跳入2;不需要,加入网络队列中
  2. 判断是否存在该网络请求的缓存,如果存在且未过期,则直接返回该缓存结果。如果不存在,则将它加入网络队列并进入流程3【注释一】
  3. 判断现有的网络队列中是否已经存在相同的网络请求,如果存在,则将该请求加入等待队列中,等待上一个相同请求的返回结果,也就是常说的请求合并
  4. 返回结果并处理

volley

过期
在上面的流程2也就是注释一的位置,Volley对于每一个缓存需要判断它们是否过期,而关于这个过期,则分为Softttl和ttl。

TTL的意思就是该缓存已经无效,必须重新从网络中获取。最终返回给调用者的是最新的那一次请求结果

而SoftTTL则会返回两次数据,第一次数据是本次的缓存数据,在返回该数据时也会将该网络请求加入到网络队列中,在请求数据返回后再次返给调用者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static class Entry {
public byte[] data;
public String etag;
public long serverDate;
public long lastModified;
public long ttl;
public long softTtl;
public Map<String, String> responseHeaders = Collections.emptyMap();

public Entry() {
}

public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}

public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}

为什么要这么设计呢?
个人思考:我觉得这个是对缓存的一个人性化设计。因为对于一个缓存来讲可能存在两种情况,第一种情况就是该缓存已经过期很久很久,完全不可用,所以就抛弃掉并重新获取。而软缓存就是那些过期不是特别久,也就是说在一定程度上可能还有些作用的缓存。例如现在有一个界面,刷新时可以让用户看到不久之前的信息,刷新结束后再将最新的数据呈现给用户。

线程管理

NetWorkDispatcher类负责从网络工作队列中取出一个请求并执行。默认情况下存在4个工作线程为他服务

1
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

而上面从定义的名字来说,是NetWork thread pool,但是实际上它并不是使用传统的线程池来管理的,而是使用一个NetWorkDispather数组来进行管理

1
2
3
4
5
6
7
8
9
10
11
12
13
private NetworkDispatcher[] mDispatchers;

public void start() {
this.stop();
this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
this.mCacheDispatcher.start();

for(int i = 0; i < this.mDispatchers.length; ++i) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
this.mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}

但是在这里就不难发现其实是存在一个问题的,因为如果工作线程发生异常退出,那么就无法生成一个新的工作线程来弥补之前这个空缺,因此这里极有可能会出现问题。当前总结的Volley版本是1.0.11,也就是第一篇博客中使用到的Volley版本,不知道现在是否已经优化

线程优先级

1
2
3
4
5
public int compareTo(Request<T> other) {
Request.Priority left = this.getPriority();
Request.Priority right = other.getPriority();
return left == right?this.mSequence.intValue() - other.mSequence.intValue():right.ordinal() - left.ordinal();
}

这里的话主要就是根据每个请求的sequence来确定的,而sequence是在add的时候由进行获取,同时自增,也就是说它是由一个计数器进行管理的。也就可以保证每一次取任务取的都是队列头部的任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public <T> Request<T> add(Request<T> request) {
request.setRequestQueue(this);
Set var2 = this.mCurrentRequests;
synchronized(this.mCurrentRequests) {
this.mCurrentRequests.add(request);
}

request.setSequence(this.getSequenceNumber());
request.addMarker("add-to-queue");
if(!request.shouldCache()) {
this.mNetworkQueue.add(request);
return request;
} else {
Map var7 = this.mWaitingRequests;
synchronized(this.mWaitingRequests) {
String cacheKey = request.getCacheKey();
if(this.mWaitingRequests.containsKey(cacheKey)) {
Object stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey);
if(stagedRequests == null) {
stagedRequests = new LinkedList();
}

((Queue)stagedRequests).add(request);
this.mWaitingRequests.put(cacheKey, stagedRequests);
if(VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", new Object[]{cacheKey});
}
} else {
this.mWaitingRequests.put(cacheKey, (Object)null);
this.mCacheQueue.add(request);
}

return request;
}
}
}


public int getSequenceNumber() {
return this.mSequenceGenerator.incrementAndGet();
}

这里需要注意一下,优先级高只是说明它先发出,并不能够确保它一定先返回

总结

Volley适合的请求为“短,小,快”,也就是说它并不适合大文件的下载操作,这是因为它存在一个内存缓存,100M的文件直接给你缓存到内存中,这样的体验就不是特别好。而如果单纯的把缓存关闭掉的话,就不如使用Retrofit + OKHttp了
上面还有一些超时重发请求,反序列化操作未总结,因为觉得这个不算什么吧,因此就此略过,而他们的使用其实也是可以自己再二次封装一下的,这样就可以让原来已经很简洁的代码变得更简洁