Volley简述
Volley是Google推出的用于Android下发起Http请求的网络库。它适合于频繁的,数据量较小的通信,而在数据量大的场景下表现不佳,原因在于,Volley对于所有的Response的解析都是在内存当中做的。官方推荐在对于大量数据通信的场景下选择其他的库,例如DownloadManager.
Volley基本原理
看图说话:
①: Volley的主要使用方法就是创建Request实例,并且将其加入到RequestQueue当中。在RequestQueue中维护了3个队列和一个Map,图中只标识最重要的两个队列NetWorkQueue和CacheQueue。如果Request不允许使用缓存,则会被直接加入到NetWorkQueue,否则被加入CacheQueue。
②:CacheDispatcher运行在单独的线程当中,不停的尝试获取CacheQueue中的Request对象。
③:CacheDispatcher一旦获取Request对象成功,就派发到Cache对象尝试获取缓存。如果找到,则直接返回Response,否则CacheDispatcher还是会将此Request对象放置于NetWorkQueue。
④: RequestQueue默认开启4个NetWorkDispatcher线程,同样不停的尝试获取NetWorkQueue当中的Request对象。
⑤: 其中某个NetWorkDispatcher获取Request对象成功,将其派发给HttpStack对象处理。HttpStack对象是真正发送接收Http报文的对象,用户可以实现它的接口,自定义发送与接收的过程。Volley默认使用HttpClient或者HttpURLConnection(根据不同SDK版本)。
⑥: HttpStack处理发送与接收。即传入Request对象,返回Response对象。
最后,无论是NetWorkDispatcher还是CacheDispatcher最后都回调Request的deliverResponse()。这也是官方文档所说的,为什么自定义的Request必须实现此方法的原因。需要注意的是,Request中必须实现的两个方法是在子线程中执行的。
Volley目录结构
volley
│ AuthFailureError.java
│ TimeoutError.java
│ VolleyError.java
│ ServerError.java
│ ClientError.java
│ ParseError.java
│ NetworkError.java
│ NoConnectionError.java
------------------------------------------------------------------------------------------
│ Cache.java //Cache接口
│ CacheDispatcher.java
│ DefaultRetryPolicy.java //重试策略,当请求发送遭遇异常,回调Request中定义的重试方法
│ ExecutorDelivery.java //ResponseDelivery实现类
│ Network.java
│ NetworkDispatcher.java
------------------------------------------------------------------------------------------
│ NetworkResponse.java //解析HTTP报文的Response数据结构
│ Request.java//定义了Resquest数据结构
│ RequestQueue.java//定义了RequestQueue数据结构
│ Response.java //在本地表示的Response,由NetworkResponse解析产生,包括数据和缓存信息
------------------------------------------------------------------------------------------
│ ResponseDelivery.java //接口
│ RetryPolicy.java //接口
│ VolleyLog.java
└─toolbox
AndroidAuthenticator.java
Authenticator.java
------------------------------------------------------------------------------------------
BasicNetwork.java // Network实现类
RequestFuture.java
------------------------------------------------------------------------------------------
DiskBasedCache.java //Cache实现类
NoCache.java//Cache实现类,即不使用缓存
------------------------------------------------------------------------------------------
HttpStack.java//接口
HttpClientStack.java //HttpStack实现类,使用HttpClient
HurlStack.java//HttpStack实现类,使用HttpURLConnection
------------------------------------------------------------------------------------------
ClearCacheRequest.java//Request的一种,用于清除所有缓存数据
StringRequest.java//Request的一种,用于请求String数据
JsonRequest.java //Request的一种,用于请求JSON数据
JsonArrayRequest.java
JsonObjectRequest.java
------------------------------------------------------------------------------------------
ImageRequest.java//Request的一种,用于加载图片
ImageLoader.java
NetworkImageView.java
------------------------------------------------------------------------------------------
HttpHeaderParser.java//工具类,可用于解析报文头部缓存信息
Volley.java//工具类,方便创建RequestQueue
PoolingByteArrayOutputStream.java
ByteArrayPool.java//工具类,Byte数组缓存池
细节剖析 -> 缓存存取的具体实现
一. 缓存的组织形式:
DiskBasedCache是将缓存按一定格式写入文件,所以在创建DiskBasedCache需要传入指定文件路径。按照代码逻辑,每一个Cache项都单独存放在一个文件当中。初始化DiskBasedCache时,读取每一个文件中的缓存头部内容,然后保存在内存Map中。
//摘取代码关键部分,与实际代码不同
@Override
public synchronized void initialize() {
File[] files = mRootDirectory.listFiles();
for (File file : files) {
fis = new BufferedInputStream(new FileInputStream(file));
CacheHeader entry = CacheHeader.readHeader(fis);
entry.size = file.length();
mEntries.put(key, entry);
}
}
二. 获取缓存
CacheDispatcher获得Request对象,就尽力获取缓存内容。
Cache.Entry entry = mCache.get(request.getCacheKey());
DiskBasedCache中,首先从内存中查找缓存头部是否存在,如果存在,再去文件中读取缓存的具体内容。
@Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
// if the entry does not exist, return.
if (entry == null) {
return null;
}
try {
cis = new CountingInputStream(new BufferedInputStream(new FileInputStream(file)));
CacheHeader.readHeader(cis); // eat header
byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
return entry.toCacheEntry(data);
}
}
三. Cache Prune
Volley会记录缓存使用空间大小,当缓存空间不足时,触发Cache Prune。遍历Map,按照插入顺序进行删除,直到空间充足。
Iterator> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
CacheHeader e = entry.getValue();
boolean deleted = getFileForKey(e.key).delete();
if (deleted) {
mTotalSize -= e.size;
} else {
VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
e.key, getFilenameForKey(e.key));
}
iterator.remove();
prunedFiles++;
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
break;
}
}
四. 缓存过期
判断缓存过期的两条规则:
- 当缓存信息的ttl小于当前系统时间,则认为过期。
- 缓存信息的soft_ttl小于当前系统时间,则认为过期
第二条规则有助于认为更改过期信息,强制系统进行缓存更新。
当Volley未找到缓存项或者缓存过期,则将Request加入NetworkQueue。但是很多网站采用ETag标签标识实体有无改变,在这种情况下,Volley仍然会发送网络请求,并根据返回304进行处理。下文将分析这种情况。
细节剖析 -> NetworkDispatcher或者CacheDispatcher完成后如何切换线程
ResponseDelivery持有MainThread的Handler引用,当一切都处理完毕,ResponseDelivery将Runnable对象发送给主线程的Handler。而此Runnable对象的任务就是回调Request的监听器。这里有两个非常重要的方法会被回调,一个是Request子类必须实现的deliverResponse
,另外一个是可选的实现RequestFinishedListener
接口的onRequestFinished
,此方法的调用时机就是当Request被处理完毕。正常情况下,deliverResponse
的调用时机在onRequestFinished
之前。另外,当Request被中止时,onRequestFinished
也会被触发。
细节剖析-> Volley处理缓存相关网络请求
如上节所述,当采用ETag标签时,即便Cache没有过期,Volley还是会发送网络请求的。
NetworkDispacher从队列中取得Request,然后交给Network发送。Network首先对Request添加Cache头,If-None-Match: Etag
,并发送。NetworkDispatcher对接收到的回复报文进行处理,刷新缓存。
对于每一次的请求,Request都会回调deliverResponse
,但是对于304的回复报文,什么内容都没有,所以,对于304回复正常报文,Volley会从cache中构造Response。
总结
Volley代码非常简洁易懂,模块之间划分清晰,适合学习~