[关闭]
@946898963 2018-05-18T12:18:46.000000Z 字数 5419 阅读 1049

Volley-CacheDispatcher源码解析

Android控件跟框架 Android源码分析


在RequestQueue中存在两个队列,一个是缓存请求队列,另一个是网络请求队列,缓存请求队列中存放的是允许使用本地缓存的请求,网络请求队列中存放的是不允许使用缓存或者是本地不存在缓存的请求。当调用RequestQueue的add()方法添加Request的时候,会根据请求的一个参数shouldCache,来判断要不要去缓存中查询,如果是去缓存中查询,那么就会把请求放到CacheQueue中。,如果不允许缓存,就直接将请求放入网络请求队列。

在RequestQueue中存在一个缓存线程,缓存线程从缓存请求队列中,取出请求,查找本地缓存,如果查找不到本地缓存,就将这个请求放入网络请求队列中去,如果查找到混存,就直接分发缓存结果。同时在RequestQueue中存在四条请求线程,负责从网絡请求队列中取出请求,从网络中获取数据。CacheDispatcher就是前面提到的缓存线程,而网络请求线程是NetworkDispatcher

此处输入图片的描述

CacheDispatcher继承自Thread,当被start后就执行它的run方法,他的run方法中会从mCacheQueue中取出请求,查找本地缓存,如果查找不到缓存,就将请求丢到mNetworkQueue中。接下来对他们的源码进行分析。

成员变量:

  1. /** The queue of requests coming in for triage. */
  2. private final BlockingQueue<Request<?>> mCacheQueue;
  3. /** The queue of requests going out to the network. */
  4. private final BlockingQueue<Request<?>> mNetworkQueue;
  5. /** The cache to read from. */
  6. private final Cache mCache;
  7. /** For posting responses. */
  8. private final ResponseDelivery mDelivery;
  9. /** Used for telling us to die. */
  10. private volatile boolean mQuit = false;
  • mCacheQueue,一个阻塞队列,就是前面提到的缓存请求队列里面存放的是允许使用本地缓存的请求
  • mNetworkQueue,一个阻塞队列,就是前面提到的网络请求队列里面存放的是不能使用本地缓存的请求,里面的请求必须从网络获取数据
  • mCache,缓存接口,具体实现类DiskBasedCache,用于进行数据缓存和缓存数据获取
  • ResponseDelivery,响应分发接口,具体实现类ExecutorDelivery,用于分发响应
  • mQuit,缓存线程的中断标志位

构造方法:

  1. public CacheDispatcher(
  2. BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
  3. Cache cache, ResponseDelivery delivery) {
  4. mCacheQueue = cacheQueue;
  5. mNetworkQueue = networkQueue;
  6. mCache = cache;
  7. mDelivery = delivery;
  8. }

构造方法就是对前面提到的成员变量进行赋值。

run方法(主要方法)

  1. @Override
  2. public void run() {
  3. if (DEBUG) VolleyLog.v("start new dispatcher");
  4. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  5. //初始化缓存
  6. mCache.initialize();
  7. while (true) {
  8. try {
  9. //从缓存队列中获取一个请求
  10. final Request<?> request = mCacheQueue.take();
  11. request.addMarker("cache-queue-take");
  12. //如果请求已经被取消,则重新获取请求
  13. if (request.isCanceled()) {
  14. request.finish("cache-discard-canceled");
  15. continue;
  16. }
  17. //根据request的cacheKey从缓存中得到对应的记录
  18. Cache.Entry entry = mCache.get(request.getCacheKey());
  19. if (entry == null) {
  20. request.addMarker("cache-miss");
  21. // 这里说明缓存中没有对应的记录,那么需要去网络中获取,那么就将它放到Network的队列中
  22. mNetworkQueue.put(request);
  23. continue;
  24. }
  25. // 如果缓存中有记录,但是已经过期了或者失效了,也需要去网络获取,放到Network队列中
  26. if (entry.isExpired()) {
  27. request.addMarker("cache-hit-expired");
  28. request.setCacheEntry(entry);
  29. mNetworkQueue.put(request);
  30. continue;
  31. }
  32. // 如果上面的情况都不存在,说明缓存中存在这样记录,那么就调用request的parseNetworkResponse方法,获取一个响应Response
  33. request.addMarker("cache-hit");
  34. Response<?> response = request.parseNetworkResponse(
  35. new NetworkResponse(entry.data, entry.responseHeaders));
  36. request.addMarker("cache-hit-parsed");
  37. if (!entry.refreshNeeded()) {
  38. // 缓存记录,不需要更新,那么就直接调用mDelivery,传回给主线程去更新。
  39. mDelivery.postResponse(request, response);
  40. } else {
  41. //还存在这样一种情况,缓存记录存在,但是它约定的生存时间已经到了(还未完全过期,叫软过期),可以将其发送到主线程去更新
  42. // 但同时,也要从网络中更新它的数据
  43. request.addMarker("cache-hit-refresh-needed");
  44. request.setCacheEntry(entry);
  45. // Mark the response as intermediate.
  46. response.intermediate = true;
  47. // 将其传回主线程的同时,将请求放到Network队列中。
  48. mDelivery.postResponse(request, response, new Runnable() {
  49. @Override
  50. public void run() {
  51. try {
  52. mNetworkQueue.put(request);
  53. } catch (InterruptedException e) {
  54. // Not much we can do about this.
  55. }
  56. }
  57. });
  58. }
  59. } catch (InterruptedException e) {
  60. // We may have been interrupted because it was time to quit.
  61. if (mQuit) {
  62. return;
  63. }
  64. continue;
  65. }
  66. }
  67. }

缓存线程(CacheDispatcher)主要做了几件事情:
1)初始化本地缓存

2)开始一个无限的循环,调用 mCacheQueue的take方法,来获得一个请求,而mCacheQueue是一个BlockingQueue,也就是说,当队列中没有请求的时候,take方法就会一直阻塞在这里,等待队列中的请求,而一旦队列中有新的请求进来了,那么它就会马上执行下去。

3)判断请求是否已经取消,如果已经被取消了,则不需要再走下去。

4)根据请求的CacheKey去缓存中寻找相对应的记录,如果找不到对应的记录,或者对应的记录过期了,则将其放到NetworkQueue队列中。

这里需要重点关注下缓存过期的情况,当缓存过期的时候,会将过期的缓存数据(首部数据和实体数据)放到Request中去,在NetworkDispatcher处理Request的时候,会检查Request中是否有缓存的数据,有缓存数据的话,会利用缓存的数据设置”If-None-Match”首部和”If-Modified-Since”首部,这样发起的请求就是条件请求了。

BasicNetwork的performRequest方法:

  1. @Override
  2. public NetworkResponse performRequest(Request<?> request) throws VolleyError {
  3. long requestStart = SystemClock.elapsedRealtime();
  4. while (true) {
  5. ....
  6. Map<String, String> headers = new HashMap<String, String>();
  7. addCacheHeaders(headers, request.getCacheEntry());
  8. httpResponse = mHttpStack.performRequest(request, headers);
  9. ....
  10. }
  11. }
  12. private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
  13. if (entry == null) {
  14. return;
  15. }
  16. if (entry.etag != null) {
  17. headers.put("If-None-Match", entry.etag);
  18. }
  19. if (entry.lastModified > 0) {
  20. Date refTime = new Date(entry.lastModified);
  21. headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
  22. }
  23. }

5)缓存中存在相对应的记录,那么调用每个请求具体的实现方法 parseNetworkResponse函数,根据具体的请求去解析得到对应的响应Response对象。

6)获得Response对象之后,还会再进行判断这个请求是不是进行一次网络的更新,这是根据记录的soft-ttl (time-to-live)属性,如下:

从这里也可以看到,expired的判断跟refreshNeed的判断是两个字段,一个是ttl,一个是softTtl。如下:

  1. /** True if the entry is expired. */
  2. public boolean isExpired() {
  3. return this.ttl < System.currentTimeMillis();
  4. }
  5. /** True if a refresh is needed from the original data source. */
  6. public boolean refreshNeeded() {
  7. return this.softTtl < System.currentTimeMillis();
  8. }

如果需要进行更新,那么就会在发送响应结果回主线程更新的同时,再将请求放到NetworkQueue中,从网络中更新请求对应的数据。如果不需要,则直接将结果调用mDelivery传回主线程进行UI的更新。

quit方法:

  1. //用于判断线程是否结束,用于退出线程
  2. private volatile boolean mQuit = false;
  3. //出线程
  4. public void quit() {
  5. mQuit = true;
  6. interrupt();
  7. }

关于volatile,建议阅读:volatile型变量的特殊访问规则

quit方法的作用是中断当前的缓存线程。quit方法中做的事情就是对中断标志位置位,同时调用interrupt()方法中断当前的缓存线程,我们知道mCacheQueue是一个阻塞队列,所以当没有请求的时候,take方法会阻塞缓存线程,这时候调用interrupt()方法会抛出InterruptedException异常,这样代码就会进入catch代码块,这时候由于中断标志位已经被置位了,所以会执行return语句,结束while循环,当前的缓存线程,就终止运行了。

  1. whike(true){
  2. try {
  3. //从缓存队列中获取一个请求
  4. final Request<?> request = mCacheQueue.take();
  5. ....
  6. } catch (InterruptedException e) {
  7. // We may have been interrupted because it was time to quit.
  8. if (mQuit) {
  9. return;
  10. }
  11. continue;
  12. }
  13. }

CacheDispatcher做的事情并不多,因为Volley主要的功能其实还是跟网络打交道,所以主要的实现,其实还是NetworkDispatcher。

Android中关于Volley的使用(六)认识 CacheDispatcher
Volley源码解析<五> CacheDispatcher和NetworkDispatcher

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注