本文系转载,转载于Volley源码分析 1,原文作者写的不错,很赞!
我们先来看一下Volley的架构图:
可以看出,Volley至少工作在3个线程当中,其中
蓝色部分为主线程:主要的工作是将请求按照优先级的顺序添加到cache的队列当中,当发出去的请求的得到相应的时候,在主线程将结果进行分发。
绿色部分为cache线程:如果cache hit,那么直接将cache中的数据进行解析,并传递给主线程,如果miss,那么则交给NetworkDispatcher进行处理。
黄色部分则为网络线程:与cache线程不同,cache只有一个线程在工作,而网络线程则可以有多个同时工作,进行网络请求,解析结果,写入cache,最终也是响应结果交给主线程。
我们以一段代码为例,跟踪代码的流程,对比上面的框架图,看看Volley到底是如何工作的。
RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html", null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// TODO Auto-generated method stub
Log.i(TAG, response.toString());
}},new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// TODO Auto-generated method stub
Log.i(TAG, error.getMessage());
}});
mQueue.add(jsonObjectRequest);
首先,我们新建了一个RequestQueue
的实例,当然在实际使用中,我们应该使用Application
的Context
作为Volley
的Context
。
然后,我们新建了一个JsonObjectRequest
,即一个Json
的请求,在其中的回调函数onResponse
中添加我们收到结果要做的事情,当然我们知道这部分代码是运行在主线程当中。
最后,将JsonObjectReques
的实例添加到RequestQueue
当中。
这就是Volley
使用的全部的代码,总体看来,我们所能看到的所有的请求都是进入RequestQueue
,然后就等待处理,得到onResponse
的响应,因此,我们可以跟随代码,从mQueue.add(jsonObjectRequest)
来分析。
- 任务的添加:
mQueue.add(jsonObjectRequest)
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与该任务队列相关联。
request.setRequestQueue(this);
//将该请求放在任务队列的待做任务当中。
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
// 设定序列号
request.setSequence(getSequenceNumber());
// 添加日志
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
// 如果该日志不需要Cache的话,那么跳过cache的队列,直接进行网络请求
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// 如果程序运行到这里,说明需要缓存cache,那么进行的操作是,先检查当前的任务有没有在cache的运行当中,
// 如果正在进行,或者说cache对应的cacheKey有Reqeust正在执行,那么则直接加入到cacheKey对应的队列当中即可。
// 如果需要cache,而且没有正在这行,则添加到等待队列和cache队列当中。
// Insert request into stage if there's already a request with the same cache key in flight.
// 同步任务队列,根据该请求是否添加到RequestQueue的不同情况,分别处理
synchronized (mWaitingRequests) {
//判断等待队列是否包含当前添加的任务
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
// 该cacheKey对应的任务之前添加过,并且还没有处理完成。则取出cacheKey对应的任务队列,将该任务添加进去。
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 {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
// 与上面不同的是,当前的任务没有处理过,所以将任务添加到等待队列中,然后添加到cache的队列中。
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
上面的代码就是RequestQueue
的add()
方法。我们可以画一下它的流程图
从中,可以看出,RequestQueue中add(Request request)
所做的工作为:
- 绑定
request
到此RequestQueue.this
- 将
request
添加到mCuurentRequest
的链表中 - 为
request
设置序列号,并打印Log
- 根据
request
是否需要cache
,如果不需要cache
,则直接将其放入mNetworkueue
当中。 - 如果
request
需要cache
,则检查该request
对应的cacheKey
(一般实际上使用URL
)是否已经在mWaitingRequests
列表中存在,如果已经存在,那么则更新cacheKey
所对应的列表,如果不存在,则将其放进mCacheQueue
中,再放入mNetworkQueue
中等待执行。 add(Request request)
方法执行完毕,返回。
此时我们也可以明白在RequestQueue
中的几个集合类的作用:
//该map的作用是用来缓存正在执行的需要Cache的Request
private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>();
//所有的需要处理的Reqeust都会在这个集合当中
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
//mCacheQueue保存的是需要从Cache中获取的Request
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
//mNetworkQueue保存的是需要网络操作的Request
private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();
对应的RequestQueue
的add
方法,我们再来看一下对应的finish(Request<?> request)
方法。
- 任务的结束:
finish(Request<?> request)
可以看出,RequestQueue
的finish(Request request)
主要做了以下的事情:
- 将
reqeust
从当前的任务列表mCurrentRequests
中删除 - 判断该
request
是否是需要cache
的请求,如果不是,则直接退出即可。 - 如果该
request
需要cache
,那么则删除所对应的cacheKey
,然后将等待的请求全部加入到mCacheQueue
即cache
的任务队列当中。其实finish()调用的并不是说任务要取消,而是说任务已经完成了,所以对应的也很容易理解了,首先该任务完成了,就应该从RequestQueue
中消失,如果不需要cache
,那么直接退出就可以啦,如果需要cache,那么就可以删除该cacheKey(URL)
对应的请求列表,因为这个时候网络任务应该已经完成,所以将剩下的任务加入到mCacheQueue
当中,让他们从cache
中获取就可以。)。
还有一点需要注意的是,该方法是的访问限定符是默认的,即包访问权限,并且在Volley
的源代码中,仅仅是Request.finish(String tag)
调用了该方法。
接下来我们进一步分析一下Request
中的finish(String tag)
方法,同时我们注意到,对于Reqeust
主要是一些标记变量和关于请求的内容,在逻辑上其他的相关的代码并不多。
/**
* Notifies the request queue that this request has finished (successfully or with error).
*
* 通知请求队列,该请求已经完成,或者成功,或者存在错误。
*
* <p>Also dumps all events from this request's event log; for debugging.</p>
* <p>导出event log的所有的时间,用来调试。</p>
*
* 该方法可能来自CacheDisptacher,ResponseDelivery,NetworkDispatcher的调用
*/
void finish(final String tag) {
// 通知RequestQueue停止该任务
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
//如果需要记录日志,则记录之。
if (MarkerLog.ENABLED) {
//获取线程Id
final long threadId = Thread.currentThread().getId();
//判断当前的Looper的线程是否为主线程,也就是判断当前的代码是否在主线程上运行。
if (Looper.myLooper() != Looper.getMainLooper()) {
// If we finish marking off of the main thread, we need to
// actually do it on the main thread to ensure correct ordering.
// 如果我们是在其他的线程(非main线程)上取消任务,那么我们需要在主线程上来完成以保证正确的顺序。
// 这边的顺序没有特别看到,为什么要这样设计。
//获取主线程的handler
Handler mainThread = new Handler(Looper.getMainLooper());
//分发取消请求的任务。
mainThread.post(new Runnable() {
@Override
public void run() {
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
}
});
return;
}
//这里就是主线程
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
} else {
long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;
if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {
VolleyLog.d("%d ms: %s", requestTime, this.toString());
}
}
}
此处代码比较简单,Request
的finish
方法主要是调用了RequestQueue
的finish()
方法来标记完成任务。进而打印相关的Log信息,此处有一点不太明白的是,为什么一定要在主线程上打印Log?
任务的取消:cancelAll(final Object tag)
/**
* Cancels all requests in this queue for which the given filter applies.
* @param filter The filtering function to use
*/
public void cancelAll(RequestFilter filter) {
synchronized (mCurrentRequests) {
for (Request<?> request : mCurrentRequests) {
if (filter.apply(request)) {
request.cancel();
}
}
}
}
/**
* Cancels all requests in this queue with the given tag. Tag must be non-null
* and equality is by identity.
*/
public void cancelAll(final Object tag) {
if (tag == null) {
throw new IllegalArgumentException("Cannot cancelAll with a null tag");
}
cancelAll(new RequestFilter() {
@Override
public boolean apply(Request<?> request) {
return request.getTag() == tag;
}
});
}
这里面才是真正的取消任务,其主要的工作如下:
同步mCurrentRequests
,因为要遍历任务,所以要同步该集合。
依次判断Request
是否符合tag
或者RequestFilter
的要去,如果符合,则取消相应的任务。
而Request
的cancel
方法则更加简单,如下:
public void cancel() {
mCanceled = true;
}
现在看来,如果要取消某个Request
,Volley
不会主动的要求Network
或者Cache
的工作线程停止当前的正在执行的Request
,而是在执行过程中,如果发现Reqeust
的mCanceled
标记被设置,那么就不再进行下一步操作。