码迷,mamicode.com
首页 > 其他好文 > 详细

volley介绍05

时间:2016-03-24 20:04:00      阅读:332      评论:0      收藏:0      [点我收藏+]

标签:

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

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

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

 

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

作为接口,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.     /** 
  12.      * Constructs an instance of the DiskBasedCache at the specified directory using 
  13.      * the default maximum cache size of 5MB. 
  14.      * @param rootDirectory The root directory of the cache. 
  15.      */  
  16.     public DiskBasedCache(File rootDirectory) {  
  17.         this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);  
  18.     }  


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

 

存取缓存,必须有存取方法,我们先从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.   
  14.        long before = mTotalSize;  
  15.        int prunedFiles = 0;  
  16.        long startTime = SystemClock.elapsedRealtime();  
  17.   
  18.        Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();  
  19.        while (iterator.hasNext()) {//遍历缓存文件信息  
  20.            Map.Entry<String, CacheHeader> entry = iterator.next();  
  21.            CacheHeader e = entry.getValue();  
  22.            boolean deleted = getFileForKey(e.key).delete();  
  23.            if (deleted) {//删除文件  
  24.                mTotalSize -= e.size;  
  25.            } else {  
  26.               VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",  
  27.                       e.key, getFilenameForKey(e.key));  
  28.            }  
  29.            iterator.remove();  
  30.            prunedFiles++;  
  31.   
  32.            if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {  
  33.                break;  
  34.            }  
  35.        }  
  36.   
  37.        if (VolleyLog.DEBUG) {  
  38.            VolleyLog.v("pruned %d files, %d bytes, %d ms",  
  39.                    prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);  
  40.        }  
  41.    }  

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

应该说没有什么特别的,其实就是把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.   
  13.         File file = getFileForKey(key);//获取缓存文件  
  14.         CountingInputStream cis = null;  
  15.         try {  
  16.             cis = new CountingInputStream(new FileInputStream(file));  
  17.             CacheHeader.readHeader(cis); // eat header读取头部  
  18.             byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));//去除头部长度  
  19.             return entry.toCacheEntry(data);  
  20.         } catch (IOException e) {  
  21.             VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());  
  22.             remove(key);  
  23.             return null;  
  24.         }  catch (NegativeArraySizeException e) {  
  25.             VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());  
  26.             remove(key);  
  27.             return null;  
  28.         } finally {  
  29.             if (cis != null) {  
  30.                 try {  
  31.                     cis.close();  
  32.                 } catch (IOException ioe) {  
  33.                     return null;  
  34.                 }  
  35.             }  
  36.         }  
  37.     }  

读取过程很简单

 

1,读取缓存文件头部

2,读取缓存文件数据

3,生成Entry,返回

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

 

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

 

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

volley介绍05

标签:

原文地址:http://www.cnblogs.com/aprz512/p/5316724.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!