自从15年8月使用Volley以来,感触很多,源码也过了几次,第一篇博客三级缓存 Volley /Lrucache/DiskLrucache + AndroidStudio导出jar包 + upload to github 就是使用Volley结合LruCache和DiskLruCache构建的三级缓存。其实关于Volley底层的实现在以前的博客中或多或少都有提及,例如缓存,队列等等,这篇博客就对它进行全方面的总结
缓存 首先,我们从缓存开始入手,毕竟这个算它的一个特点,也是一个优势。 我的第一篇博客实现三级缓存就利用的Volley的网络层缓存,LruCache的内存缓存和DiskLruCache的磁盘缓存。
Volley是实现了网络层缓存和内存缓存的。
网络请求的发起流程
检测是否开启缓存,如果开启,跳入2;不需要,加入网络队列中
判断是否存在该网络请求的缓存,如果存在且未过期,则直接返回该缓存结果。如果不存在,则将它加入网络队列并进入流程3【注释一】
判断现有的网络队列中是否已经存在相同的网络请求,如果存在,则将该请求加入等待队列中,等待上一个相同请求的返回结果,也就是常说的请求合并
返回结果并处理
过期 在上面的流程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了 上面还有一些超时重发请求,反序列化操作未总结,因为觉得这个不算什么吧,因此就此略过,而他们的使用其实也是可以自己再二次封装一下的,这样就可以让原来已经很简洁的代码变得更简洁