volley介绍05

----------------------------------------------------------------------------------

转载:http://blog.csdn.net/crazy__chen/article/details/46494627

----------------------------------------------------------------------------------

从上一篇文章我们已经知道,现在要处理的问题就是CacheDispatcher和NetworkDispatcher怎么分别去缓存和网络获取数据的问题,这两个问题我分开来讲。

但是首先说明的是,这两个问题其实是有联系的,当CacheDispatcher获取不到缓存的时候,会将request放入网络请求队列,从而让NetworkDispatcher去处理它;

而当NetworkDispatcher获得数据以后,又会将数据缓存,下次CacheDispatcher就可以从缓存中获得数据了。

这篇文章,就让我们先来了解volley是怎么从缓存中获取数据的。

第一个要说明的,当然是CacheDispatcher类,这个类本质是一个线程,作用就是根据request从缓存中获取数据

我们先来看它的构造方法

[java] view plain copy

  1. /**
  2. * Creates a new cache triage dispatcher thread.  You must call {@link #start()}
  3. * in order to begin processing.
  4. * 创建一个调度线程
  5. * @param cacheQueue Queue of incoming requests for triage
  6. * @param networkQueue Queue to post requests that require network to
  7. * @param cache Cache interface to use for resolution
  8. * @param delivery Delivery interface to use for posting responses
  9. */
  10. public CacheDispatcher(
  11. BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
  12. Cache cache, ResponseDelivery delivery) {
  13. mCacheQueue = cacheQueue;//缓存请求队列
  14. mNetworkQueue = networkQueue;//网络请求队列
  15. mCache = cache;//缓存
  16. mDelivery = delivery;//响应分发器
  17. }

从上面的方法看出,CacheDispatcher持有缓存队列cacheQueue,目的当然是为了从队列中获取东西。

而同时持有网络队列networkQueue,目的是为了在缓存请求失败后,将request放入网络队列中。

至于响应分发器delivery是成功请求缓存以后,将响应分发给对应请求的,分发器存在的目的我已经在前面的文章中说过几次了,就是为了灵活性和在主线程更新UI(至于怎么做到,我们以后会讲)

最后是一个缓存类cache,这个cache可以看成是缓存的代表,也就是说它就是缓存,是面向对象思想的体现,至于它是怎么实现的,等下会说明

看完构造方法,我们就直奔对Thread而言,最重要的run()方法

[java] view plain copy

  1. @Override
  2. public void run() {
  3. if (DEBUG) VolleyLog.v("start new dispatcher");
  4. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//设置线程优先级
  5. // Make a blocking call to initialize the cache.
  6. mCache.initialize();//初始化缓存对象
  7. while (true) {
  8. try {
  9. // Get a request from the cache triage queue, blocking until
  10. // at least one is available.
  11. // 从缓存队列中取出请求
  12. final Request<?> request = mCacheQueue.take();
  13. request.addMarker("cache-queue-take");
  14. // If the request has been canceled, don‘t bother dispatching it.
  15. if (request.isCanceled()) {//是否取消请求
  16. request.finish("cache-discard-canceled");
  17. continue;
  18. }
  19. // Attempt to retrieve this item from cache.
  20. Cache.Entry entry = mCache.get(request.getCacheKey());//获取缓存
  21. if (entry == null) {
  22. request.addMarker("cache-miss");
  23. // Cache miss; send off to the network dispatcher.
  24. mNetworkQueue.put(request);//如果没有缓存,放入网络请求队列
  25. continue;
  26. }
  27. // If it is completely expired, just send it to the network.
  28. if (entry.isExpired()) {//如果缓存超时
  29. request.addMarker("cache-hit-expired");
  30. request.setCacheEntry(entry);
  31. mNetworkQueue.put(request);
  32. continue;
  33. }
  34. // We have a cache hit; parse its data for delivery back to the request.
  35. request.addMarker("cache-hit");
  36. Response<?> response = request.parseNetworkResponse(//解析响应
  37. new NetworkResponse(entry.data, entry.responseHeaders));
  38. request.addMarker("cache-hit-parsed");
  39. if (!entry.refreshNeeded()) {//不需要更新缓存
  40. // Completely unexpired cache hit. Just deliver the response.
  41. mDelivery.postResponse(request, response);
  42. } else {
  43. // Soft-expired cache hit. We can deliver the cached response,
  44. // but we need to also send the request to the network for
  45. // refreshing.
  46. request.addMarker("cache-hit-refresh-needed");
  47. request.setCacheEntry(entry);
  48. // Mark the response as intermediate.
  49. response.intermediate = true;
  50. // Post the intermediate response back to the user and have
  51. // the delivery then forward the request along to the network.
  52. mDelivery.postResponse(request, response, new Runnable() {
  53. @Override
  54. public void run() {
  55. try {
  56. mNetworkQueue.put(request);
  57. } catch (InterruptedException e) {
  58. // Not much we can do about this.
  59. }
  60. }
  61. });
  62. }
  63. } catch (InterruptedException e) {
  64. // We may have been interrupted because it was time to quit.
  65. if (mQuit) {
  66. return;
  67. }
  68. continue;
  69. }
  70. }
  71. }

这个方法里面做了很多事情,我们按顺序看

1,从缓存请求队列中取出request

2,判断这个request已经是否被取消,如果是,调用它的finish()方法,continue

3,否则,利用Cache获得缓存,获得缓存的依据是request.getCacheKey(),也就是request的url

4,如果缓存不存在,将request放入mNetworkQueue,continue

5,否则,检查缓存是否过期,是,同样将request放入mNetworkQueue,continue

6,否则,检查是否希望更新缓存,否,组装成response交给分发器mDelivery

7,否则组装成response交给分发器mDelivery,并且将request再加入mNetworkQueue,去网络请求更新

OK,上面的过程已经说得够清楚了。让人疑惑的很重要一步,就是Cache这个类到底是怎么获取缓存数据的,下面我们就来看看Cache这个类。

这个Cache其实是一个接口(面向抽象编程的思想),而它的具体实现,我们在第一篇文章的Volley类中看到,是DiskBasedCache类。

无论如何,我们先看接口

[java] view plain copy

  1. /**
  2. * An interface for a cache keyed by a String with a byte array as data.
  3. * 缓存接口
  4. */
  5. public interface Cache {
  6. /**
  7. * Retrieves an entry from the cache.
  8. * @param key Cache key
  9. * @return An {@link Entry} or null in the event of a cache miss
  10. */
  11. public Entry get(String key);
  12. /**
  13. * Adds or replaces an entry to the cache.
  14. * @param key Cache key
  15. * @param entry Data to store and metadata for cache coherency, TTL, etc.
  16. */
  17. public void put(String key, Entry entry);
  18. /**
  19. * Performs any potentially long-running actions needed to initialize the cache;
  20. * will be called from a worker thread.
  21. * 初始化
  22. */
  23. public void initialize();
  24. /**
  25. * Invalidates an entry in the cache.
  26. * @param key Cache key
  27. * @param fullExpire True to fully expire the entry, false to soft expire
  28. */
  29. public void invalidate(String key, boolean fullExpire);
  30. /**
  31. * Removes an entry from the cache.
  32. * @param key Cache key
  33. */
  34. public void remove(String key);
  35. /**
  36. * Empties the cache.
  37. */
  38. public void clear();
  39. /**
  40. * Data and metadata for an entry returned by the cache.
  41. * 缓存数据和元数据记录类
  42. */
  43. public static class Entry {
  44. /**
  45. * The data returned from cache.
  46. * 缓存数据
  47. */
  48. public byte[] data;
  49. /**
  50. * ETag for cache coherency.
  51. * 统一的缓存标志
  52. */
  53. public String etag;
  54. /**
  55. * Date of this response as reported by the server.
  56. * 响应日期
  57. */
  58. public long serverDate;
  59. /**
  60. * The last modified date for the requested object.
  61. *  最后修改日期
  62. */
  63. public long lastModified;
  64. /**
  65. * TTL for this record.
  66. * Time To Live 生存时间
  67. */
  68. public long ttl;
  69. /** Soft TTL for this record. */
  70. public long softTtl;
  71. /**
  72. * Immutable response headers as received from server; must be non-null.
  73. * 响应头,必须为非空
  74. */
  75. public Map<String, String> responseHeaders = Collections.emptyMap();
  76. /**
  77. * True if the entry is expired.
  78. * 是否超时
  79. */
  80. public boolean isExpired() {
  81. return this.ttl < System.currentTimeMillis();
  82. }
  83. /**
  84. * True if a refresh is needed from the original data source.
  85. * 缓存是否需要更新
  86. */
  87. public boolean refreshNeeded() {
  88. return this.softTtl < System.currentTimeMillis();
  89. }
  90. }
  91. }

作为接口,Cache规定了缓存初始化,存取等必须的方法让子类去继承。

比较重要的是,其内部有一个Entry静态内部类,这个类Entry可以理解成一条缓存记录,也就是说每个Entry就代表一条缓存记录。

这么一说,上面run()方法里面的代码就比较好理解了,我们就知道,为什么Cache获取的缓存,叫做Entry。

然后我们来看DiskBasedCache,从名字上知道,这个类是硬盘缓存的意思

在这里我们注意到,volley其实只提供了硬盘缓存而没有内存缓存的实现,这可以说是它的不足,也可以说它作为一个扩展性很强的框架,是留给使用者自己实现的空间。如果我们需要内存缓存,我们大可自己写一个类继承Cache接口。

在这之前,我们先来看volley是怎么实现硬盘缓存的

首先是构造函数

[java] view plain copy

  1. /**
  2. * Constructs an instance of the DiskBasedCache at the specified directory.
  3. * @param rootDirectory The root directory of the cache.
  4. * @param maxCacheSizeInBytes The maximum size of the cache in bytes.
  5. */
  6. public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
  7. mRootDirectory = rootDirectory;
  8. mMaxCacheSizeInBytes = maxCacheSizeInBytes;
  9. }
  10. /**
  11. * Constructs an instance of the DiskBasedCache at the specified directory using
  12. * the default maximum cache size of 5MB.
  13. * @param rootDirectory The root directory of the cache.
  14. */
  15. public DiskBasedCache(File rootDirectory) {
  16. this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
  17. }

这个函数传入了两个参数,一个是指缓存根目录,一个是指缓存的最大值

存取缓存,必须有存取方法,我们先从put方法看起

[java] view plain copy

  1. /**
  2. * Puts the entry with the specified key into the cache.
  3. * 存储缓存
  4. */
  5. @Override
  6. public synchronized void put(String key, Entry entry) {
  7. pruneIfNeeded(entry.data.length);//修改当前缓存大小使之适应最大缓存大小
  8. File file = getFileForKey(key);
  9. try {
  10. FileOutputStream fos = new FileOutputStream(file);
  11. CacheHeader e = new CacheHeader(key, entry);//缓存头,保存缓存的信息在内存
  12. boolean success = e.writeHeader(fos);//写入缓存头
  13. if (!success) {
  14. fos.close();
  15. VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
  16. throw new IOException();
  17. }
  18. fos.write(entry.data);//写入数据
  19. fos.close();
  20. putEntry(key, e);
  21. return;
  22. } catch (IOException e) {
  23. }
  24. boolean deleted = file.delete();
  25. if (!deleted) {
  26. VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
  27. }
  28. }

这个方法一看比较复杂,我先来说明一下主要的存储过程

1,检查要缓存的数据的长度,如果当前已经缓存的数据大小mTotalSize加上要缓存的数据大小,大于缓存最大值mMaxCacheSizeInBytes,则要将旧的缓存文件删除,以腾出空间来存储新的缓存文件

2,根据缓存记录类Entry,提取Entry除了数据以外的其他信息,例如这个缓存的大小,过期时间,写入日期等,并且将这些信息实例成CacheHeader,。这样做的目的是,方便以后我们查询缓存,获得缓存相应信息时,不需要去读取硬盘,因为CacheHeader是内存中的。

3,写入缓存

根据上面步奏,我们来读pruneIfNeeded()方法,这个方法就是完成了步奏1的工作,主要思路是不断删除文件,直到腾出足够的空间给新的缓存文件

[java] view plain copy

  1. /**
  2. * Prunes the cache to fit the amount of bytes specified.
  3. * 修剪缓存大小,去适应规定的缓存比特数
  4. * @param neededSpace The amount of bytes we are trying to fit into the cache.
  5. */
  6. private void pruneIfNeeded(int neededSpace) {
  7. if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {//如果没有超过最大缓存大小,返回
  8. return;
  9. }
  10. if (VolleyLog.DEBUG) {
  11. VolleyLog.v("Pruning old cache entries.");
  12. }
  13. long before = mTotalSize;
  14. int prunedFiles = 0;
  15. long startTime = SystemClock.elapsedRealtime();
  16. Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
  17. while (iterator.hasNext()) {//遍历缓存文件信息
  18. Map.Entry<String, CacheHeader> entry = iterator.next();
  19. CacheHeader e = entry.getValue();
  20. boolean deleted = getFileForKey(e.key).delete();
  21. if (deleted) {//删除文件
  22. mTotalSize -= e.size;
  23. } else {
  24. VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
  25. e.key, getFilenameForKey(e.key));
  26. }
  27. iterator.remove();
  28. prunedFiles++;
  29. if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
  30. break;
  31. }
  32. }
  33. if (VolleyLog.DEBUG) {
  34. VolleyLog.v("pruned %d files, %d bytes, %d ms",
  35. prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
  36. }
  37. }

在这个方法中,我们注意到有一个mEntries,我们看一下它的声明

[java] view plain copy

  1. /**
  2. * Map of the Key, CacheHeader pairs
  3. * 缓存记录表,用于记录所有的缓存文件信息
  4. * 使用LRU算法
  5. */
  6. private final Map<String, CacheHeader> mEntries =
  7. new LinkedHashMap<String, CacheHeader>(16, .75f, true);

也就是说它实则保存了所有缓存的头信息CacheHeader,而且在map中,这些信息是按照LRU算法排列的,LRU算法是LinkedHashMap的内置算法。

每次存取缓存,都会修改这个map,也就是说要调用LRU算法进行重新排序,这样造成一定效率的下降,但貌似也没有更好的方法。

然后就是第二步,根据Entry生成CacheHeader,我们来看一下CacheHeader这个内部类

[java] view plain copy

  1. /**
  2. * Handles holding onto the cache headers for an entry.
  3. * 缓存基本信息类
  4. */
  5. // Visible for testing.
  6. static class CacheHeader {
  7. /** The size of the data identified by this CacheHeader. (This is not
  8. * serialized to disk.
  9. * 缓存数据大小
  10. * */
  11. public long size;
  12. /**
  13. * The key that identifies the cache entry.
  14. * 缓存键值
  15. */
  16. public String key;
  17. /** ETag for cache coherence. */
  18. public String etag;
  19. /**
  20. * Date of this response as reported by the server.
  21. * 保存日期
  22. */
  23. public long serverDate;
  24. /**
  25. * The last modified date for the requested object.
  26. * 上次修改时间
  27. */
  28. public long lastModified;
  29. /**
  30. * TTL for this record.
  31. * 生存时间
  32. */
  33. public long ttl;
  34. /** Soft TTL for this record. */
  35. public long softTtl;
  36. /**
  37. * Headers from the response resulting in this cache entry.
  38. * 响应头
  39. */
  40. public Map<String, String> responseHeaders;
  41. private CacheHeader() { }
  42. /**
  43. * Instantiates a new CacheHeader object
  44. * @param key The key that identifies the cache entry
  45. * @param entry The cache entry.
  46. */
  47. public CacheHeader(String key, Entry entry) {
  48. this.key = key;
  49. this.size = entry.data.length;
  50. this.etag = entry.etag;
  51. this.serverDate = entry.serverDate;
  52. this.lastModified = entry.lastModified;
  53. this.ttl = entry.ttl;
  54. this.softTtl = entry.softTtl;
  55. this.responseHeaders = entry.responseHeaders;
  56. }
  57. /**
  58. * Reads the header off of an InputStream and returns a CacheHeader object.
  59. * 读取缓存头信息
  60. * @param is The InputStream to read from.
  61. * @throws IOException
  62. */
  63. public static CacheHeader readHeader(InputStream is) throws IOException {
  64. CacheHeader entry = new CacheHeader();
  65. int magic = readInt(is);
  66. if (magic != CACHE_MAGIC) {
  67. // don‘t bother deleting, it‘ll get pruned eventually
  68. throw new IOException();
  69. }
  70. entry.key = readString(is);
  71. entry.etag = readString(is);
  72. if (entry.etag.equals("")) {
  73. entry.etag = null;
  74. }
  75. entry.serverDate = readLong(is);
  76. entry.lastModified = readLong(is);
  77. entry.ttl = readLong(is);
  78. entry.softTtl = readLong(is);
  79. entry.responseHeaders = readStringStringMap(is);
  80. return entry;
  81. }
  82. /**
  83. * Creates a cache entry for the specified data.
  84. */
  85. public Entry toCacheEntry(byte[] data) {
  86. Entry e = new Entry();
  87. e.data = data;
  88. e.etag = etag;
  89. e.serverDate = serverDate;
  90. e.lastModified = lastModified;
  91. e.ttl = ttl;
  92. e.softTtl = softTtl;
  93. e.responseHeaders = responseHeaders;
  94. return e;
  95. }
  96. /**
  97. * Writes the contents of this CacheHeader to the specified OutputStream.
  98. * 写入缓存头
  99. */
  100. public boolean writeHeader(OutputStream os) {
  101. try {
  102. writeInt(os, CACHE_MAGIC);
  103. writeString(os, key);
  104. writeString(os, etag == null ? "" : etag);
  105. writeLong(os, serverDate);
  106. writeLong(os, lastModified);
  107. writeLong(os, ttl);
  108. writeLong(os, softTtl);
  109. writeStringStringMap(responseHeaders, os);
  110. os.flush();
  111. return true;
  112. } catch (IOException e) {
  113. VolleyLog.d("%s", e.toString());
  114. return false;
  115. }
  116. }
  117. }

应该说没有什么特别的,其实就是把Entry类里面的,出来data以外的信息提取出来而已。

另外还增加了两个读写方法,readHeader(InputStream is)和writeHeader(OutputStream os)

从这两个方法可以知道,对于一个缓存文件来说,前面是关于这个缓存的一些信息,然后才是真正的缓存数据。

最后一步,写入缓存数据,将CacheHeader添加到map

[java] view plain copy

  1. fos.write(entry.data);//写入数据
  2. fos.close();
  3. putEntry(key, e);

OK,到此为止,写入就完成了。那么读取,就是写入的逆过程而已。

[java] view plain copy

  1. /**
  2. * Returns the cache entry with the specified key if it exists, null otherwise.
  3. * 查询缓存
  4. */
  5. @Override
  6. public synchronized Entry get(String key) {
  7. CacheHeader entry = mEntries.get(key);
  8. // if the entry does not exist, return.
  9. if (entry == null) {
  10. return null;
  11. }
  12. File file = getFileForKey(key);//获取缓存文件
  13. CountingInputStream cis = null;
  14. try {
  15. cis = new CountingInputStream(new FileInputStream(file));
  16. CacheHeader.readHeader(cis); // eat header读取头部
  17. byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));//去除头部长度
  18. return entry.toCacheEntry(data);
  19. } catch (IOException e) {
  20. VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
  21. remove(key);
  22. return null;
  23. }  catch (NegativeArraySizeException e) {
  24. VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
  25. remove(key);
  26. return null;
  27. } finally {
  28. if (cis != null) {
  29. try {
  30. cis.close();
  31. } catch (IOException ioe) {
  32. return null;
  33. }
  34. }
  35. }
  36. }

读取过程很简单

1,读取缓存文件头部

2,读取缓存文件数据

3,生成Entry,返回

相信大家都可以看懂,因为真的没有那么复杂,我就不再累述了。

get(),put()方法看过以后,其实DiskBasedCache类还有一些public方法,例如缓存信息map的初始化,例如删除所有缓存文件的方法,这些都比较简单,基本上就是利用get,put方法里面的函数就可以完成,我也不再贴出代码来说明了。

DiskBasedCache给大家讲解完毕,整个从缓存中获取数据的过程,相信也说得很清楚。

时间: 2024-08-07 20:57:07

volley介绍05的相关文章

volley介绍06

----------------------------------------------------------------------------- 转载:http://blog.csdn.net/crazy__chen/article/details/46506921 ----------------------------------------------------------------------------- 上一篇文章当中,我介绍了CacheDispatcher和缓存类Ca

volley介绍04

------------------------------------------------------------------------------- 转载:http://blog.csdn.net/crazy__chen/article/details/46490635 ------------------------------------------------------------------------------- 上一篇文章给大家说明了Request<T>的内部结构,对

volley介绍03

------------------------------------------------------------------------------ 转载:http://blog.csdn.net/crazy__chen/article/details/46486123 ------------------------------------------------------------------------------ 在上一篇文章中,我们已经提到volley的使用方式和设计的整体

volley介绍08

----------------------------------------------------------------------------------- 转载:http://blog.csdn.net/crazy__chen/article/details/46612901 ----------------------------------------------------------------------------------- 在上篇文章中,我们最终通过网络,获取到了H

GoogleIO 2013 Android快速联网框架Volley介绍

最近调研Android开发框架,看了xutils,KjFramework等框架的HTTP模块,觉得都太简单了,只是简单封装了HttpUrlConnenction和Handler,加了个回调函数,感觉就是比自己写一个好一点点.后面发现了Volley这个比较靠谱的Android网络请求框架,就用它了. Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮. 这是Volley名称的由来: a burst or emission of many things or a lar

Android高级_第三方下载工具Volley

Volley下载主要应用于下载文本数据和图片数据两个方向,下面分别介绍: 一.使用Volley开启下载,首先要做的是导包和添加权限: (1)在build.gradle文件中导入依赖包:compile 'eu.the4thfloor.volley:com.android.volley:2015.05.28': (2)在清单文件中添加访问网络权限,读.写外存权限: 二.文本内容上传下载步骤: (1)创建RequestQueue对象,用于发送请求的请求队列:同时创建StringRequest对象,用于

Android Volley入门到精通:初识Volley的基本用法

1. Volley简介 我们平时在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据.Android系统中主要提供了两种方式来进行HTTP通信,HttpURLConnection和HttpClient,几乎在任何项目的代码中我们都能看到这两个类的身影,使用率非常高. 不过HttpURLConnection和HttpClient的用法还是稍微有些复杂的,如果不进行适当封装的话,很容易就会写出不少重复代码.于是乎,一些Android

【Android高级】应用开发必须要掌握的框架&lt;Volley&gt;

开发久了,就会发现掌握一个好的应用框架是多么的重要,虽然是别人的东西,你也许不能完全搞懂其中的原理,但你知道如何利用其到自己的开发中,这样不仅能节省大量的时间,而且别人沉淀下来的精华效果一定比他的厉害之处.Volley就是一个这么好的一个东西,发现两个大神总结的太好,我再总结就感觉造次了,详解如下所示: 1.Volley介绍 2.Volley用法 版权声明:本文为博主原创文章,未经博主允许不得转载.

volley 讲解

总结的不错的Volley介绍: Volley主页 https://android.googlesource.com/platform/frameworks/volley http://www.youtube.com/watch?v=yhv8l9F44qo&feature=player_embedded 1. 什么是Volley 在这之前,我们在程序中需要和网络通信的时候,大体使用的东西莫过于AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClien