zoukankan      html  css  js  c++  java
  • 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.   
    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给大家讲解完毕,整个从缓存中获取数据的过程,相信也说得很清楚。

  • 相关阅读:
    常用公式 距离、波形、力
    代码字体
    关于flash缩放的详细解释
    色调
    工程项目1
    使用double无法得到数学上的精确结果的原因及为何不能用double来初始化BigDecimal
    第一次测验感受
    原码,补码,反码的概念及Java中使用那种存储方式
    static的含义
    第一次测试代码
  • 原文地址:https://www.cnblogs.com/aprz512/p/5316724.html
Copyright © 2011-2022 走看看