写在前面
最近在整理之前写的项目,当时开发的时候对于Volley没有深入的研究过,这次拿出来进行下源码分析。虽然Volley框架目前已经有些过时,但是里面的思想对于个人来说还是很有成长价值。
整体过程描述
我们使用Volley进行联网获取接口数据的整体流程是怎样的呢?我们很有必要在深入代码细节讲解之前从整体上把握下,这样将有助于我们更好的理解volley的源码
首先,我们要使用volley进行网络访问获取数据,那么volley就需要先做一些初始化的工作:
创建缓存文件“volley”,并设置最大为5M的大小。
根据我们手机当前版本,选择合适的网络操作类(httpClient、httpUrlConnection)进行网络操作类的一系列的封装和初始化。
- 开启一个缓存线程和四个网络线程,对任务请求队列一直进行监听,当有任务到来的时候,它们就会按照逻辑执行。对于缓存线程而言,它需要初始化缓存文件夹volley,将当前文件夹中的缓存加载到内存;对于网络线程而言,它还构建了从主线程到子线程通讯的通道(Handler)。
以上就是volley的初始化做的主要工作了。之后,我们肯定要实例化一个request类,将这个request添加到上述讲到的任务等待队列进行执行。那么后续就是缓存线程和网络线程之间的操作了:
首先判断该请求是否设置了缓存选项(默认为应该缓存),如果设置了不缓存,那么该请求被放置到网络访问等待执行序列。如果是可以进行缓存,那再看是否是第一次发出,如果是第一次发出,那么volley将会对该请求缓存。
如果1中的request属于不缓存的request,那么在网络线程将会在网络访问等待执行序列中取出request进行执行,那么就可以得到网络访问之后的结果。其结果通过Handler提供给request抽象类的实现类,例如StringRequest,我们就可以在UI线程中获取到网络执行之后的结果了;
如果1中的request属于可缓存:
- 但是是第一次访问的情况的话,那么在缓存线程会将该request直接加入到网络访问等待执行序列。之后得到结果之后首先进行缓存,之后通过handler返回结果给UI线程。
- 如果request不是第一次访问,那么要检查缓存中保存的该request是否过期,是否“新鲜”;如果已经过期或者不“新鲜”,那么会将该request直接加入到网络访问等待执行序列;反之的话,将使用缓存中保存的数据返回给UI线程。
文字也许看着比较累,下面我们使用图例来展示下Volley的初始化和整体流程
Volley源码细节分析
初始调用
首先对于Volley的使用大家应该比较熟悉了,我们分析源码就从Volley暴露出来的接口开始即可,下面是一个很简单的Volley请求代码:
RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this);
StringRequest stringRequest = new StringRequest("https://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
requestQueue.add(stringRequest);
暴露的Volley类
下面让我们看下Volley具体的执行过程:
对于第一行代码而言,最重要的就是volley的newRequestQueue方法了,我们看下源码:
/** Default on-disk cache directory. */
private static final String DEFAULT_CACHE_DIR = "volley";
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
//初始化名字为“Volley”的缓存文件
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
//初始化网络访问类
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
//当前SDK版本大于9,则内部由封装HttpUrlConnection类的HurlStack实现
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
//低版本SDK,则由封装HttpClient类的HttpClientStack实现
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
//进一步封装网络请求类,得到BasicNetwork类实例,该类实例用于进行处理网络访问
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
HurlStack && HttpClientStack介绍
上述根据SDK版本不同而进行区分使用的实质在于使用网络访问类的不同,即HttpClient类与HttpUrlConnection类使用差别,本文不展开讲述,可自行查阅。
DiskBasedCache类介绍
下面我们查看第35行的源码,首先是DiskBasedCache构造函数:
/** Default maximum disk usage in bytes. */
private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
public DiskBasedCache(File rootDirectory) {
this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
}
public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
mRootDirectory = rootDirectory;
mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
由上可知:缓存文件最大可支持5M的大小,并且实质上DiskBasedCache类就是Volley中唯一实现Cache接口的缓存类
//根据URL得到缓存文件夹中的entry数据
@Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
// if the entry does not exist, return.
if (entry == null) {
return null;
}
File file = getFileForKey(key);
CountingInputStream cis = 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);
} catch (IOException e) {
VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
remove(key);
return null;
} finally {
if (cis != null) {
try {
cis.close();
} catch (IOException ioe) {
return null;
}
}
}
}
//将本地缓存文件加载到内存
@Override
public synchronized void initialize() {
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
return;
}
File[] files = mRootDirectory.listFiles();
if (files == null) {
return;
}
for (File file : files) {
BufferedInputStream fis = null;
try {
fis = new BufferedInputStream(new FileInputStream(file));
CacheHeader entry = CacheHeader.readHeader(fis);
entry.size = file.length();
putEntry(entry.key, entry);
} catch (IOException e) {
if (file != null) {
file.delete();
}
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ignored) { }
}
}
}
//向缓存中写入数据(根据URL创建缓存文件,将entry实体进行保存)
@Override
public synchronized void put(String key, Entry entry) {
pruneIfNeeded(entry.data.length);
File file = getFileForKey(key);
try {
BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
CacheHeader e = new CacheHeader(key, entry);
boolean success = e.writeHeader(fos);
if (!success) {
fos.close();
VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
throw new IOException();
}
fos.write(entry.data);
fos.close();
putEntry(key, e);
return;
} catch (IOException e) {
}
boolean deleted = file.delete();
if (!deleted) {
VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
}
}
//根据缓存key值进行缓存子文件名称定义:
//1.对url前半段的串进行hash计算
//2.对url后半段的串进行hash计算
//cacheFileName = 1+2的值
private String getFilenameForKey(String key) {
int firstHalfLength = key.length() / 2;
String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
return localFilename;
}
/**
* Returns a file object for the given cache key.
*/
public File getFileForKey(String key) {
return new File(mRootDirectory, getFilenameForKey(key));
}
RequestQueue类介绍
接下来要讲的就是RequestQueue类和他的start方法了:
//设定网络线程个数
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
//存放request的缓存型优先堵塞队列
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
//存放request的网络执行型优先堵塞队列
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
//ExecutorDelivery类实现了ResponseDelivery接口
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// 实例化缓存线程,并执行
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// 实例化4个网络线程并开始执行
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
RequestQueue这个类除了start方法比较重要,另外一个重要的地方在哪里呢?我们还记得在activity中最后需要把具体的request加入到RequestQueue中吧?让我们来看下RequestQueue类的add方法实现
//Request任务等待序列,存储的是具有相同url请求地址的Request对象
private final Map<String, Queue<Request<?>>> mWaitingRequests =
new HashMap<String, Queue<Request<?>>>();
//当前正在执行的Request序列集合
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
//缓存线程的核心处理数据源:Request缓存优先阻塞队列
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
//网络线程的核心处理数据源:Request的缓存优先阻塞队列
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
public <T> Request<T> add(Request<T> request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
//将新加进来的request添加到当前任务集合
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// 如果request要求不进行缓存,
//则直接将该request加入"网络型"优先阻塞队列
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
//对于正在执行的request的时候又加入了同样的request请求的话
//会将后续的request添加到等待序列,直到当前request执行完毕
//执行完毕之后,会调用finish方法,将该类request清空
if (mWaitingRequests.containsKey(cacheKey)) {
// 属于同一类request,则统一放在一个队列里(LinkedList)
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// 当前等待序列并没有返现有执行该类request
//则将该request加入等待序列,并加入到“缓存型”优先阻塞队列,等待缓存线程来执行。
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
<T> void finish(Request<T> request) {
// Remove from the set of requests currently being processed.
synchronized (mCurrentRequests) {
//将执行完毕的request在当前执行request集合中清除
mCurrentRequests.remove(request);
}
synchronized (mFinishedListeners) {
for (RequestFinishedListener<T> listener : mFinishedListeners) {
listener.onRequestFinished(request);
}
}
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null) {
if (VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
// Process all queued up requests. They won't be considered as in flight, but
// that's not a problem as the cache has been primed by 'request'.
mCacheQueue.addAll(waitingRequests);
}
}
}
}
add方法思路:
1. 查看request是否需要被缓存,如果不需要则直接交给网络优先阻塞队列去执行
2. 对于多个相同request的请求的操作处理:
- 将加进来的request放在一个hashSet的集合中,该set集合表示肯定要执行的request
接着request会被加到一个waitingRequest,类型为hashMap。key为request的url,value为linkedList,linkedlist保存相同key的request。
以上保证了相同的request不会执行多次,只是会执行一次。
当前一个request执行完毕,会将set集合中的request该实例删除,并将根据url删除waitingRequest中对应的value值。最后将linkedList中的所有request添加进入缓存阻塞队列
ExecutorDelivery类介绍
在上述代码中比较好理解,我们先来看下ExecutorDelivery类,ExecutorDelivery类是一个很重要的类,它主要实现了缓存线程与UI线程切换与信息发送、网络线程与UI线程切换与信息发送的功能:
public class ExecutorDelivery implements ResponseDelivery {
/** Used for posting responses, typically to the main thread. */
private final Executor mResponsePoster;
//ExecutorDelivery构造方法之一,主要完成子线程与UI线程的切换
public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
/**
* Creates a new response delivery interface, mockable version
* for testing.
* @param executor For running delivery tasks
*/
public ExecutorDelivery(Executor executor) {
mResponsePoster = executor;
}
//以下Override标识的三个方法,为ResponseDelivery接口的实现方法
@Override
public void postResponse(Request<?> request, Response<?> response) {
postResponse(request, response, null);
}
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
@Override
public void postError(Request<?> request, VolleyError error) {
request.addMarker("post-error");
Response<?> response = Response.error(error);
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
}
/**
* A Runnable used for delivering network responses to a listener on the
* main thread.
*/
@SuppressWarnings("rawtypes")
//该Runnable对象实质上已经是被切换到UI线程执行
private class ResponseDeliveryRunnable implements Runnable {
private final Request mRequest;
private final Response mResponse;
private final Runnable mRunnable;
public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
mRequest = request;
mResponse = response;
mRunnable = runnable;
}
@SuppressWarnings("unchecked")
@Override
public void run() {
// If this request has canceled, finish it and don't deliver.
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// 将调用Request类的具体实现类方法deliverResponse,将结果返回到最上层
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
// If this is an intermediate response, add a marker, otherwise we're done
// and the request can be finished.
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
//普通request请求将在这里被清除出当前任务执行序列和任务等待序列
//Request.finish最后调用RequestQueue的finish方法
//具体查看上述RequestQueue中finish源码
mRequest.finish("done");
}
// If we have been provided a post-delivery runnable, run it.
if (mRunnable != null) {
mRunnable.run();
}
}
}
}
上述代码的核心也就是使用handler进行线程的切换了,如果对于handler机制不太了解,可以查看我的这篇handler的博文:Android消息机制讲解
需要指出的是第73和第75行代码中执行的deliverResponse()和deliverError()方法。实质上这两个方法都是抽象类Request中的方法:
//具体实现Request抽象类的request实现该方法(例如StringRequest)
abstract protected void deliverResponse(T response);
public void deliverError(VolleyError error) {
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
随便查找一个实现Request抽象类的实体类,比如StringRequest,它的deliverResponse()实现如下:
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
到了这里,是不是感觉代码变得有点眼熟?我们在创建一个StringRequest实例的时候,构造方法实现中就使用到了mListener和mErrorListener。这样我们就可以得到我们发送request之后得到的结果了。
介绍完ExecutorDelivery类,再让我们回到RequestQueue类的start方法上:接下来的关注核心就是两个类型的线程了:即缓存线程CacheDispatcher类和网络线程NetworkDispatcher类,两个类都继承自Thread类,下面我们依次进行源码剖析:
CacheDispatcher类介绍
CacheDispatcher继承了Thread类,主要工作就是加载本地缓存文件,将UI线程请求的Request的情况进行筛选比较,来决定Request的走向:可以是将请求加入到网络执行等待队列去执行、也可以是拿出缓存中该Request的应答response直接返回UI线程等等…我们接下来看他的run方法的实现(已经去掉不重要的部分代码):
@Override
public void run() {
//设置当前线程级别:后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 将本地的缓存文件加载到内存
mCache.initialize();
while (true) {
try {
// 一直从“缓存型”优先阻塞队列中获取请求
final Request<?> request = mCacheQueue.take();
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
//如果执行过程中取消该request,则将request在当前执行序列中除去并且在等待序列中除去具有相同URL的队列
//(在讲解完毕网络线程源码之后会详细介绍)
request.finish("cache-discard-canceled");
continue;
}
//先检查本地缓存,如果本地缓存中没有对应的已经封装好response的Entry类
//则将该request加入“网络型”优先阻塞队列
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// 如果本地有缓存,但是已经过期,则将该request加入“网络型”优先阻塞队列
//(可以结合下面的网络线程保存response缓存一起看)
if (entry.isExpired()) {
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// 根据本地缓存生成一个response回应实体类
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
if (!entry.refreshNeeded()) {
// 是否需要重新刷新数据,不需要则将该缓存生成的response返回给UI线程
mDelivery.postResponse(request, response);
} else {
//需要重新刷新数据
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
//将该request加入到“网络型”优先阻塞队列
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
}
}
以上就是缓存线程的run方法的全部实现了,该方法的涉及到的技术点总结如下:
基于阻塞队列实现的“生产者”与“消费者”线程模型
基于Http缓存机制实现的判断本地缓存是否过期等问题
以上这两个知识点是理解缓存线程实现机制的前提条件,由于篇幅有限,本文不再过多涉及这两个方面知识点,请读者自行查阅。 了解Http缓存机制的知识可以点击这里
以上CacheDispatcher类的源码已经基本剖析完毕,接下来我们看下NetworkDispatcher类的具体源码
NetworkDispatcher类介绍
NetworkDispatcher类也是继承自Thread,同样最重要的方法就是里面的run方法了:
@Override
public void run() {
//设置当前线程为后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// 从阻塞队列中拿出一个请求,如果队列为空,则阻塞等待
request = mQueue.take();
} catch (InterruptedException e) {
if (mQuit) {
return;
}
continue;
}
try {
if (request.isCanceled()) {
//请求被取消,在当前执行序列和等待序列中删除该request
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// 根据请求得到networkResponse对象
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
//返回304响应,并且之前已经处理过该请求,则略过
request.finish("not-modified");
continue;
}
// 根据networkResponse中数据生成response.cacheEntry,并且返回response实例
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
//将request和对应的response相应保存到本地缓存
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// 标记该request已经被处理
request.markDelivered();
//调用ExecutorDelivery.postResponse方法,相应源码在上面已经贴出
//即将处理结果返回给UI线程
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
}
NetworkDispatcher类的主要功能如上,已经在代码中较为详细的做了解释说明。需要指出的是第28行代码中mNetwork.performRequest方法和第37行的request.parseNetworkResponse方法,下面我们依次来分析下他们的源码。mNetwork是BasicNetwork类的实例,那么我们先来看下BasicNetwork的源码实现:
BasicNetwork源码介绍
BasicNetwork类是NetWork接口的唯一实现,主要用来进行网络操作的封装和网络响应的处理(各种返回码的判断)。核心方法就是performRequest了:
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = Collections.emptyMap();
try {
// Gather headers.
Map<String, String> headers = new HashMap<String, String>();
//将该请求加上"If-None-Match"、"If-Modified-Since"
//此处属于进行http的对比缓存策略
addCacheHeaders(headers, request.getCacheEntry());
httpResponse = mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
Entry entry = request.getCacheEntry();
if (entry == null) {
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}
//如果是304响应,而且request保留着entry缓存,
//则直接使用本地缓存构建一个NetworkResponse返回
entry.responseHeaders.putAll(responseHeaders);
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
entry.responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}
// Some responses such as 204s do not have content. We must check.
if (httpResponse.getEntity() != null) {
//将httpResponse值写入
responseContents = entityToBytes(httpResponse.getEntity());
} else {
// Add 0 byte response as a way of honestly representing a
// no-content request.
responseContents = new byte[0];
}
// if the request is slow, log it.
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusLine);
if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
//一般的新请求从这里进行返回NetworkResponse实例
return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
SystemClock.elapsedRealtime() - requestStart);
} catch (SocketTimeoutException e) {
attemptRetryOnException("socket", request, new TimeoutError());
} catch (ConnectTimeoutException e) {
attemptRetryOnException("connection", request, new TimeoutError());
} catch (MalformedURLException e) {
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {
int statusCode = 0;
NetworkResponse networkResponse = null;
if (httpResponse != null) {
statusCode = httpResponse.getStatusLine().getStatusCode();
} else {
throw new NoConnectionError(e);
}
VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
if (responseContents != null) {
networkResponse = new NetworkResponse(statusCode, responseContents,
responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
statusCode == HttpStatus.SC_FORBIDDEN) {
attemptRetryOnException("auth",
request, new AuthFailureError(networkResponse));
} else {
// TODO: Only throw ServerError for 5xx status codes.
throw new ServerError(networkResponse);
}
} else {
throw new NetworkError(networkResponse);
}
}
}
}
Request抽象类
Request类是一个抽象类,实现它的子类主要要实现parseNetworkResponse抽象方法。我们这里使用的时StringRequest类,那么我们看下StringRequest的实现:
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
上述代码比较简单,根据response的编码方式对response数据进行编码并返回String类型的数据源。之后调用了Response.success方法,第二个形参执行了HttpHeaderParser.parseCacheHeaders函数:
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
long now = System.currentTimeMillis();
Map<String, String> headers = response.headers;
long serverDate = 0;
long lastModified = 0;
long serverExpires = 0;
long softExpire = 0;
long finalExpire = 0;
long maxAge = 0;
long staleWhileRevalidate = 0;
boolean hasCacheControl = false;
boolean mustRevalidate = false;
String serverEtag = null;
String headerValue;
headerValue = headers.get("Date");
if (headerValue != null) {
serverDate = parseDateAsEpoch(headerValue);
}
headerValue = headers.get("Cache-Control");
if (headerValue != null) {
hasCacheControl = true;
String[] tokens = headerValue.split(",");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.equals("no-cache") || token.equals("no-store")) {
mustRevalidate = true;
} else if (token.startsWith("max-age=")) {
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
}
} else if (token.startsWith("stale-while-revalidate=")) {
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e) {
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
mustRevalidate = true;
}
}
}
headerValue = headers.get("Expires");
if (headerValue != null) {
serverExpires = parseDateAsEpoch(headerValue);
}
headerValue = headers.get("Last-Modified");
if (headerValue != null) {
lastModified = parseDateAsEpoch(headerValue);
}
serverEtag = headers.get("ETag");
// Cache-Control takes precedence over an Expires header, even if both exist and Expires
// is more restrictive.
if (hasCacheControl) {
softExpire = now + maxAge * 1000;
finalExpire = mustRevalidate
? softExpire
: softExpire + staleWhileRevalidate * 1000;
} else if (serverDate > 0 && serverExpires >= serverDate) {
// Default semantic for Expire header in HTTP specification is softExpire.
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.etag = serverEtag;
entry.softTtl = softExpire;
entry.ttl = finalExpire;
entry.serverDate = serverDate;
entry.lastModified = lastModified;
entry.responseHeaders = headers;
return entry;
}
看到上面的代码我们是不是就豁然开朗了?这个方法执行的就是将response中所有的重要数据进行转换生成Volley自己的缓存对象Entry的过程:包括Http缓存字段以及请求应答Data在内的所有数据都进行了保存。如果对Http缓存策略还是比较陌生的话可以查看这篇博文:彻底弄懂HTTP缓存机制及原理
以上,基本上完成了Volley框架的核心模块的介绍,包含:Volley的网络线程执行原理、缓存线程执行原理、本地缓存情况介绍、http网络缓存的处理(先强制缓存策略后对比缓存策略)等等。
总结
以上源码可能一时看上去似懂非懂,那么我们来总结下:
Volley初始化:首先创建缓存文件,之后初始化网络访问类BasicNetwork,构建一个缓存线程CacheDispatcher和四个网络线程NetworkDispatcher,并开始执行他们的run方法,一直等待request到来。
一个request被加入到RequestQueue中,首先判断该Request是否需要缓存,如果不需要则直接进入网络线程NetworkDispatcher等待执行;否则进入缓存线程CacheDispatcher等待执行。
- 对于2中被加入缓存线程CacheDispatcher的Request来说,如果该Request在本地没有缓存,那么CacheDispatcher就会把它交给NetworkDispatcher去处理;如果该Request有缓存,那么CacheDispatcher会检查该Request对应的缓存是否过期是否需要刷新资源(Http的强制缓存策略),如果没有过期不需要刷新资源的话,CacheDispatcher线程会自己构建一个response通过ExecutorDelivery的postResponse方法将结果返回UI线程;如果缓存已经过期,或者是需要刷新资源,那么CacheDispatcher线程会将Request交给NetworkDispatcher;
对于NetworkDispatcher来说,根据request的来源不同有如下逻辑:
如果Request不需要被缓存,则Request将被执行并且得到返回结果返回UI线程
如果Request是从CacheDispatcher而来,并且本地没有缓存过,那么NetworkDispatcher执行完该Request,会将该Request的URL和对应的Respone以Cache.Entry实体类(包含Http缓存策略以及应答数据等信息)的形式保存在本地缓存
如果Request是从CacheDispatcher而来,本地缓存有但是已经过期,那么会将本地缓存http缓存数据加入到header中,也就是加入”If-None-Match”、”If-Modified-Since”数据,发送给服务器进行对比缓存过程。如果服务器返回304,那么代表该资源还可以继续使用,则将该request和对应的缓存再次更新到本地缓存文件中,之后将缓存中的Respone返回UI线程。如果不可用,则返回200,接下来照样是覆盖本地缓存文件,将结果返回给UI线程
终于,Volley框架的大概流程已经讲述完毕,虽然我们只使用了StringRequest来进行发送网络访问的演示,但是其他的继承Request类的具体实体类的核心逻辑过程也是一样的。
特殊说明
需要特别指出的是:imageLoader的使用,通过阅读Volley源码,我们知道Volley已经为我们做了外存层面的缓存,我们需要自己根据业务逻辑是否来实现内存缓存,如果我们自己实现内存缓存的话(当然自己也可以实现外存的缓存,但是2个外存缓存那就没啥意思了= =),那么Volley框架优先使用我们自己定义的内存缓存,具体的源码不在展示,很简单(最后的Request也是被加入到RequestQueue中)。内存缓存的接口Volley已经为我们写好:
//ImageLoader的第二个形参就是我们自己实现的内存缓存(基于LRU)类
ImageLoader imageLoader = new ImageLoader(requestQueue, new BitmapCache());
ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView,
R.mipmap.ic_launcher, R.mipmap.ic_launcher);
imageLoader.get("https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg", listener);
BitmapCache.java
public class BitmapCache implements ImageLoader.ImageCache {
private LruCache<String, Bitmap> mCache;
public BitmapCache() {
int maxSize = 10 * 1024 * 1024;
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
}
@Override
public Bitmap getBitmap(String url) {
return mCache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
}
如上,我们的处理image的逻辑已经完美。
另外一点需要指出的就是缓存OOM的问题:毕竟Volley的本地缓存是有限的,一直使用总有缓存爆满的情况,那么我们需要手动清理缓存(也算是比较略坑的地方)
我们可以通过Cache.remove(String url)来移除对应url的缓存文件,如:
mRequestQueue.getCache().remove(url);
clear()清空总缓存,我们可以通过调用Cache.clear();来清空缓存,如下:
mRequestQueue.getCache().clear();