buder

201709011工作日记--Volley源码详解(二)

1.Cache接口和DiskBasedCache实现类

  首先,DiskBasedCache类是Cache接口的实现类,因此我们需要先把Cache接口中的方法搞明白。

 

    

  首先分析下Cache接口中的东西,首先是接口的内部类 class entry{},用途是返回缓存的数据,下面是内部类的具体实现:

 

 1 class Entry {
 2         /**
 3          * 从缓存中返回的数据
 4          * The data returned from cache.
 5          * */
 6         public byte[] data;
 7 
 8         /**
 9          * 缓存一致性
10          * ETag for cache coherency. */
11         public String etag;
12 
13         /**
14          * 从服务端返回的响应数据
15          * Date of this response as reported by the server. */
16         public long serverDate;
17 
18         /**
19          * 请求的最后修改时间
20          * The last modified date for the requested object. */
21         public long lastModified;
22 
23         /**
24          * 记录的TTL值。IP包被路由器丢弃之前允许通过的最大网段数量
25          * 作用是限制IP数据包在计算机网络中的存在的时间,IP数据包在计算机网络中可以转发的最大跳数
26          * TTL for this record. */
27         public long ttl;
28 
29         /** Soft TTL for this record. */
30         public long softTtl;
31 
32         /** Immutable response headers as received from server; must be non-null. */
33         public Map<String, String> responseHeaders = Collections.emptyMap();
34 
35         /**过期,返回true
36          * True if the entry is expired. */
37         public boolean isExpired() {
38             return this.ttl < System.currentTimeMillis();
39         }
40 
41         /**
42          * 如果需要从数据源跟新数据则返回TRUE
43          * True if a refresh is needed from the original data source. */
44         public boolean refreshNeeded() {
45             return this.softTtl < System.currentTimeMillis();
46         }
47     }

 

  上面的是在接口中定义了一个内部类Entry,这种情况之前没见过,还不知道有什么具体作用,下面是接口内部类举例:

  

 1 public class QuestionMain implements ITest{
 2     public static void main(String[] args) {
 3         ITest.TestInternal.test(new QuestionMain());
 4     }
 5     @Override
 6     public void print() {
 7         System.out.println("真正实现方法");
 8     }
 9 }
10 interface ITest {
11     void print();
12     public static class TestInternal {
13         public static void test(ITest test) {
14             test.print();
15         }
16     }
17 }

 

  链接中有一点点相关讨论,但是仍然不知道有啥作:http://www.th7.cn/Program/java/201412/329066.shtmlhttp://bbs.csdn.net/topics/390735550

我的一点理解是:由于接口中的所有方法都需要重写,但是有些方法不想重写的话可以放到接口内部类中去。

  接口 Cache 里面封装了一个静态内部类 Entry(登记),这个内部类非常重要,看了 HTTP 协议的同学们会发现,Entry 里面定义的这些成员变量跟 headers(消息报头)里面关于缓存的标签是一样的,这也是前面强调要看协议的原因。其中还维护了一个map 用来保存消息报头中的 key / value,data 来保存 entity 消息实体。除此之外就是一些集合操作了。接口的内部类 Entry,他真的太重要了,我们看连个判断过期和需要刷新的方法分别是,两个成员变量跟当前时间的对比。而 data 是二进制数组,我们都知道在 HTTP 中 start line 和 headers 是明文存储的,而 Entity 是没有规定的,一般我们都用二进制流传输,可以减少传输流量,并且安全,data 这里就是用来保存 Entity 的。

  这个接口的作用就是对外提供一个查询、存入、删除缓存条目的作用。

  Cache 的默认实现是 DiskBasedCache,实现类的目的是:类的目的是 将文件直接缓存到指定目录中的硬盘上的缓存实现。

  参数与初始化函数:

 1 /**
 2      * 缓存条目
 3      * Map of the Key, CacheHeader pairs */
 4     private final Map<String, CacheHeader> mEntries =
 5             new LinkedHashMap<String, CacheHeader>(16, .75f, true);
 6 
 7     /**
 8      * 目前所使用到的缓存字节数组
 9      * Total amount of space currently used by the cache in bytes. */
10     private long mTotalSize = 0;
11 
12     /**
13      * 缓存的根目录
14      * The root directory to use for the cache. */
15     private final File mRootDirectory;
16 
17     /**
18      * 缓存的最大字节数组
19      * The maximum size of the cache in bytes. */
20     private final int mMaxCacheSizeInBytes;
21 
22     /**
23      * 默认的最大硬盘缓存
24      * Default maximum disk usage in bytes. */
25     private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
26 
27     /** High water mark percentage for the cache */
28     private static final float HYSTERESIS_FACTOR = 0.9f;
29 
30     /** Magic number for current version of cache file format. */
31     private static final int CACHE_MAGIC = 0x20150306;
32 
33     /**缓存根目录,最大缓存字节数
34      * Constructs an instance of the DiskBasedCache at the specified directory.
35      * @param rootDirectory The root directory of the cache.
36      * @param maxCacheSizeInBytes The maximum size of the cache in bytes.
37      */
38     public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
39         mRootDirectory = rootDirectory;
40         mMaxCacheSizeInBytes = maxCacheSizeInBytes;
41     }
42 
43     /**
44      * Constructs an instance of the DiskBasedCache at the specified directory using
45      * the default maximum cache size of 5MB.
46      * @param rootDirectory The root directory of the cache.
47      */
48     public DiskBasedCache(File rootDirectory) {
49         this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
50     }

 

  针对缓存数据的处理:

 

  1  /**
  2      * 清空缓存,并将缓存目录下面的所有缓存文件删除
  3      * Clears the cache. Deletes all cached files from disk.
  4      */
  5     @Override
  6     public synchronized void clear() {
  7         File[] files = mRootDirectory.listFiles();
  8         if (files != null) {
  9             for (File file : files) {
 10                 file.delete();
 11             }
 12         }
 13             mEntries.clear();
 14         mTotalSize = 0;
 15         VolleyLog.d("Cache cleared.");
 16     }
 17 
 18     /**
 19      * 利用指定----key,去获取缓存条目
 20      * Returns the cache entry with the specified key if it exists, null otherwise.
 21      */
 22     @Override
 23     public synchronized Entry get(String key) {
 24         CacheHeader entry = mEntries.get(key);
 25         // if the entry does not exist, return.
 26         //缓存为空则直接返回
 27         if (entry == null) {
 28             return null;
 29         }
 30         File file = getFileForKey(key);
 31         try {
 32             CountingInputStream cis = new CountingInputStream(
 33                     new BufferedInputStream(createInputStream(file)), file.length());
 34             try {
 35                 CacheHeader entryOnDisk = CacheHeader.readHeader(cis);
 36                 if (!TextUtils.equals(key, entryOnDisk.key)) {
 37                     // File was shared by two keys and now holds data for a different entry!
 38                     VolleyLog.d("%s: key=%s, found=%s",
 39                             file.getAbsolutePath(), key, entryOnDisk.key);
 40                     // Remove key whose contents on disk have been replaced.
 41                     removeEntry(key);
 42                     return null;
 43                 }
 44                 byte[] data = streamToBytes(cis, cis.bytesRemaining());
 45                 return entry.toCacheEntry(data);
 46             } finally {
 47                 // Any IOException thrown here is handled by the below catch block by design.
 48                 //noinspection ThrowFromFinallyBlock
 49                 cis.close();
 50             }
 51         } catch (IOException e) {
 52             VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
 53             remove(key);
 54             return null;
 55         }
 56     }
 57 
 58     /**
 59      * Initializes the DiskBasedCache by scanning for all files currently in the
 60      * specified root directory. Creates the root directory if necessary.
 61      */
 62     @Override
 63     public synchronized void initialize() {
 64         if (!mRootDirectory.exists()) {
 65             if (!mRootDirectory.mkdirs()) {
 66                 VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
 67             }
 68             return;
 69         }
 70         File[] files = mRootDirectory.listFiles();
 71         if (files == null) {
 72             return;
 73         }
 74         for (File file : files) {
 75             try {
 76                 long entrySize = file.length();
 77                 CountingInputStream cis = new CountingInputStream(
 78                         new BufferedInputStream(createInputStream(file)), entrySize);
 79                 try {
 80                     CacheHeader entry = CacheHeader.readHeader(cis);
 81                     // NOTE: When this entry was put, its size was recorded as data.length, but
 82                     // when the entry is initialized below, its size is recorded as file.length()
 83                     entry.size = entrySize;
 84                     putEntry(entry.key, entry);
 85                 } finally {
 86                     // Any IOException thrown here is handled by the below catch block by design.
 87                     //noinspection ThrowFromFinallyBlock
 88                     cis.close();
 89                 }
 90             } catch (IOException e) {
 91                 //noinspection ResultOfMethodCallIgnored
 92                 file.delete();
 93             }
 94         }
 95     }
 96 
 97     /**
 98      * Invalidates an entry in the cache.
 99      * @param key Cache key
100      * @param fullExpire True to fully expire the entry, false to soft expire
101      */
102     @Override
103     public synchronized void invalidate(String key, boolean fullExpire) {
104         Entry entry = get(key);
105         if (entry != null) {
106             entry.softTtl = 0;
107             if (fullExpire) {
108                 entry.ttl = 0;
109             }
110             put(key, entry);
111         }
112     }
113 
114     /**
115      * Puts the entry with the specified key into the cache.
116      */
117     @Override
118     public synchronized void put(String key, Entry entry) {
119         pruneIfNeeded(entry.data.length);
120         File file = getFileForKey(key);
121         try {
122             BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(file));
123             CacheHeader e = new CacheHeader(key, entry);
124             boolean success = e.writeHeader(fos);
125             if (!success) {
126                 fos.close();
127                 VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
128                 throw new IOException();
129             }
130             fos.write(entry.data);
131             fos.close();
132             putEntry(key, e);
133             return;
134         } catch (IOException e) {
135         }
136         boolean deleted = file.delete();
137         if (!deleted) {
138             VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
139         }
140     }
141 
142     /**
143      * Removes the specified key from the cache if it exists.
144      */
145     @Override
146     public synchronized void remove(String key) {
147         boolean deleted = getFileForKey(key).delete();
148         removeEntry(key);
149         if (!deleted) {
150             VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
151                     key, getFilenameForKey(key));
152         }
153     }
154 
155     /**为指定的缓存键创建伪唯一文件名。利用key,创建key相关的文件名
156      * Creates a pseudo-unique filename for the specified cache key.
157      * @param key The key to generate a file name for.
158      * @return A pseudo-unique filename.
159      */
160     private String getFilenameForKey(String key) {
161         int firstHalfLength = key.length() / 2;
162         String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
163         localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
164         return localFilename;
165     }
166 
167     /**
168      * 根据给定的缓存键,创建该文件,并返回该键对应的文件对象
169      * Returns a file object for the given cache key.
170      */
171     public File getFileForKey(String key) {
172         return new File(mRootDirectory, getFilenameForKey(key));
173     }
174 
175     /**
176      * 修剪缓存以适应指定的字节数
177      * Prunes the cache to fit the amount of bytes specified.
178      * @param neededSpace The amount of bytes we are trying to fit into the cache.
179      */
180     private void pruneIfNeeded(int neededSpace) {
181         //如果已经用的和需要用的大小,小于最大缓存字节数,则直接返回
182         if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
183             return;
184         }
185         if (VolleyLog.DEBUG) {
186             VolleyLog.v("Pruning old cache entries.");
187         }
188 
189         long before = mTotalSize;
190         int prunedFiles = 0;
191         long startTime = SystemClock.elapsedRealtime();
192 
193         Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
194         while (iterator.hasNext()) {
195             Map.Entry<String, CacheHeader> entry = iterator.next();
196             CacheHeader e = entry.getValue();
197             boolean deleted = getFileForKey(e.key).delete();
198             if (deleted) {
199                 mTotalSize -= e.size;
200             } else {
201                VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
202                        e.key, getFilenameForKey(e.key));
203             }
204             iterator.remove();
205             prunedFiles++;
206 
207             if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
208                 break;
209             }
210         }
211 
212         if (VolleyLog.DEBUG) {
213             VolleyLog.v("pruned %d files, %d bytes, %d ms",
214                     prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
215         }
216     }
217 
218     /**
219      * 利用特定的key去添加缓存条目
220      * Puts the entry with the specified key into the cache.
221      * @param key The key to identify the entry by.
222      * @param entry The entry to cache.
223      */
224     private void putEntry(String key, CacheHeader entry) {
225         //存入条目之前需要需要更改缓存容量的大小
226         if (!mEntries.containsKey(key)) {
227             mTotalSize += entry.size;
228         } else {
229             CacheHeader oldEntry = mEntries.get(key);
230             mTotalSize += (entry.size - oldEntry.size);
231         }
232         mEntries.put(key, entry); //存入缓存条目
233     }
234 
235     /**
236      * 删除缓存并且更改缓存大小
237      * Removes the entry identified by 'key' from the cache.
238      */
239     private void removeEntry(String key) {
240         CacheHeader removed = mEntries.remove(key);
241         if (removed != null) {
242             mTotalSize -= removed.size;
243         }
244     }

 

  实现类里面还有一个静态内部类CacheHeader,作用是将缓存一个条目。其中pruneIfNeeded()方法是在 put 方法的第一行执行的,做的就是这件事,Volley 并没有使用 LRU。而是使用的 FIFO。DiskBasedCache 剩下的就是一些文件操作了,就不挨着看了。

 

  这里顺便提一下内部接口,具体例子在Response.java这个类中,存在着两个接口:

 

 1  /** Callback interface for delivering parsed responses.  传递相应信息时,回调此接口*/
 2     public interface Listener<T> {
 3         /** Called when a response is received. */
 4         void onResponse(T response);
 5     }
 6 
 7     /** Callback interface for delivering error responses.    传递错误响应信息时,回调此接口*/
 8     public interface ErrorListener {
 9         /**
10          * Callback method that an error has been occurred with the
11          * provided error code and optional user-readable message.
12          */
13         void onErrorResponse(VolleyError error);
14     }

 

  看下内部接口的使用场景:

 

 1 package com.dao.util;  
 2   
 3 public class Util {  
 4     public interface Worker {  
 5         void work();  
 6     }  
 7 }  
 8     
 9 package com.dao.util;  
10   
11 import com.dao.util.Util.Worker;  
12   
13 public class Demo implements Worker {  
14     public void work() {  
15         System.out.println("Is working");  
16     }  
17 }

 

  在这个util类里面封装了会改变的Worker,也就是说,util类里面有自己的算法:一部分是固定,另外一部分是可变的,而这个可变的部分就变成了一个接口,接口是特殊的抽象类~其实的意思大概的这个worker可能在不同的工作环境有不同的工作方式,所以在类里面放一个接口不是什么新的设计,而是一种思想方式,让代码扩展性更高。

 

2. 创建一个 StringRequest 请求

  这个是我们发起的请求,我们可以注意上边的方法有四个参数的方法,(int method, String url, Listener listener,ErrorListener errorListener) ,我们可以主要关注一个后边的两个回调,一个是成功的回调,一个是失败的回调,而且我们可以看一下他的源码。如下为利用构造函数创建的一个StringRequest请求代码:

 

 1 StringRequest stringRequest = new StringRequest("http://www.baidu.com",
 2                 new Response.Listener<String>() {
 3                     @Override
 4                     public void onResponse(String response) {
 5 
 6                     }
 7                 }, new Response.ErrorListener() {
 8             @Override
 9             public void onErrorResponse(VolleyError error) {
10 
11             }
12         });

 

 

 

先看下StringRequest继承了Request类,需要实现其中的两个抽象方法;在构造函数中需要两个回调函数。他们之间的关系如下:

 

 

StringRequest类:

 

 

 1 /**
 2  * 一个用于在给定URL中以String形式检索响应正文的请求。
 3  * A canned request for retrieving the response body at a given URL as a String.
 4  */
 5 public class StringRequest extends Request<String> {
 6     private final Listener<String> mListener;
 7 
 8     /**
 9      * 利用给定的方式创造一个请求消息
10      * Creates a new request with the given method.
11      *
12      * @param method the request {@link Method} to use  请求方式
13      * @param url URL to fetch the string at            用于获取字符串的URL
14      * @param listener Listener to receive the String response  侦听器接收String响应
15      * @param errorListener Error listener, or null to ignore errors        错误侦听器,或null忽略错误
16      */
17     public StringRequest(int method, String url, Listener<String> listener,
18             ErrorListener errorListener) {
19         super(method, url, errorListener);
20         mListener = listener;
21     }
22 
23     /**
24      * 使用指定的GET方式创建新的请求消息
25      * Creates a new GET request.
26      *
27      * @param url URL to fetch the string at
28      * @param listener Listener to receive the String response
29      * @param errorListener Error listener, or null to ignore errors
30      */
31     public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
32         this(Method.GET, url, listener, errorListener);
33     }
34 
35     /**
36      * 将解析到的响应传送给他们的监听器,给定的响应response保证为非空值;
37      * 无法解析的响应不会传递。
38      * @param response The parsed response returned by
39      */
40     @Override
41     protected void deliverResponse(String response) {
42         if (mListener != null) {
43             mListener.onResponse(response);
44         }
45     }
46     /**
47      * 工作线程将会调用这个方法
48      * 方法的目的是:按照HTTP头消息中的编码字符集,去重新构建这个消息的返回信息
49      *
50      * new String(byte[],"UTF-8")是新建了一个UTF-8编码的字符串
51      *
52      * @param response Response from the network
53      * @return  返回成功响应的消息
54      */
55     @Override
56     protected Response<String> parseNetworkResponse(NetworkResponse response) {
57         String parsed;
58         try {
59             //解析头部信息的编码格式,然后将返回信息按照编码格式进行转换
60             parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
61         } catch (UnsupportedEncodingException e) {//如果指定字符集不受支持
62             parsed = new String(response.data);
63         }
64         return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
65     }
66 }

 

 

 

Response类的内部接口:

 

 

 1 public class Response<T> {
 2 
 3     /** Callback interface for delivering parsed responses.  传递相应信息时,回调此接口*/
 4     public interface Listener<T> {
 5         /** Called when a response is received. */
 6         void onResponse(T response);
 7     }
 8 
 9     /** Callback interface for delivering error responses.    传递错误响应信息时,回调此接口*/
10     public interface ErrorListener {
11         /**
12          * Callback method that an error has been occurred with the
13          * provided error code and optional user-readable message.
14          */
15         void onErrorResponse(VolleyError error);
16     }

 

posted on 2017-09-11 08:15  buder  阅读(140)  评论(0编辑  收藏  举报

导航