在上一篇文章中,我们主要分析了Volley一次网络请求的总体流程,并在此基础上初步分析了Request和RequestQueue两个Volley框架中较为重要的类。
而本片文章,将在上一篇【进阶android】Volley源码分析——Volley的流程的基础上,更加深入结合Volley的源代码,进一步分析Volley的处理流程,及关于Volley两种线程的处理流程分析。
本文的结构如下:首先我们会分析网络线程的处理流程;接着会在网络线程的基础上分析缓存线程。
对网络流程的分析,我们主要侧重点于该子线程本身的处理逻辑;而对缓存线程的分析,我们则主要侧重于缓存线程与主线程之间的交互。
一、网络线程;
根据上一篇文章的分析,我们知道网络线程对应的类是NetWorkDispatcher类;同时,默认情况下Volley框架会开启4个网络线程(size为4的网络线程池)。
整个NetWorkDispatcher类的逻辑并不复杂,其属性无非是网络请求队列以及从RequestQueue类中传入的NetWork、Cache及ResponseDelivery三个引用。
由于NetWorkDispatcher类是一个线程的映射类,所以其主要的方法就是run方法;run方法定义了网络线程所有的处理流程,而网络线程的作用就是根据一个Request对象,执行一次网络请求,并将请求的结果(响应或者错误)传递给主线程的过程。
下面我们就根据这一认识,具体分析NetWorkDispatcher类中的run方法:
public void run() {
//将网络线程的优先级设置为后台运行线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request request;
while (true) {
try {
// Take a request from the queue.
//第一步
request = mQueue.take();//从请求队列之中获取顶部的Request对象
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
//第二步
if (request.isCanceled()) {
request.finish("network-discard-cancelled");//结束请求
continue;
}
...
// Perform the network request.第三步
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.第四步
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.第五步
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) {第六步
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
mDelivery.postResponse(request, response);//将响应传递至UI线程,第七步
} catch (VolleyError volleyError) {
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
mDelivery.postError(request, new VolleyError(e));
}
}
}
根据以上源码,可以看出run方法中的流程是一个典型的子线程处理流程;一个无限的while循环,一个决定是否退出无限循环的Boolean变量mQuit以及while循环之中的一次逻辑处理流程。
对于一次处理流程逻辑,我们可以很容易的分析出一次处理逻辑共有七个步骤:
1、从请求队列中take一个Request对象;NetWorkDispatcher类中的变量mQueue是一个BlockingQueue<Request>对象的引用;BlockingQueue是一个接口,表示一个阻塞队列;而其take方法的作用则是获取并移除该队列的头部元素;如果队列无头部元素,则该方法会一直阻塞直到出现一个可用的头部元素位置。
2、根据第一步获取的Request对象,判断该Request对象是否已经在其他线程被取消了(例如主线程);如果该对象已经被取消了,则直接调用Request对象的finish方法结束流程,从而不进行网络请求;
3、调用NetWork接口的performRequest方法进行真正的网络请求操作,并将原始的HttpResponse转换成NetWorkResponse对象;关于NetWork的performRequest方法的具体实现我们将在下文具体分析;至于NetWorkResponse类,它则是按照响应码、响应头参数、响应内容以及服务端的响应内容是否修改简单、初步的解析了一下原始HTTPResponse,它相当于原始HTTPResponse与最终Response<T>之间的一个中间过渡。
4、根据第三步的NetWorkResponse对象判断服务端的内容是否被修改(例如服务器上的一张图片)?如果响应码为304,则表示服务端的内容自上一次访问后就在未改变;此时,如若当前Request对象已经被发送到UI线程,则结束当前流程,直接返回;
5、调用Request对象的parseNetworkResponse方法将NetWorkResponse对象转换为最终的Response<T>对象;对于Request类而言,parseNetworkResponse方法是一个抽象方法,它要求子类根据自己的个性化定义这个方法的功能;而这个抽象方法的实质是将HTTPResponse原始响应中的原始响应内容(一般为byte)转换(解析)为一个通用的格式,例如一串字符串、一个JSON对象或者一张Bitmap;
6、如果Request对象需要缓存,且Response具有其对应的Cache.Entry,则调用Cache接口的put方法将Response对应的Cache.Entry进行缓存;对于Cache接口如何缓存,请参考下一篇关于Volley的分析文章【进阶android】Volley源码分析——Volley的缓存;
7、调用ResponseDelivery接口的postResponse方法,将Request对象和Response对象作为参数传递给主线程;如何传递,我们将在下面的缓存线程分析中侧重介绍。
通过以上7步的分析,我们大致知道了网络线程的一次逻辑处理的流程。
在分析完网络线程的一次逻辑处理的流程后,我们重点再看看第三步,也就是具体执行网络请求的这一步骤。
根据上文,第三个是通过NetWork接口的performRequest方法来具体执行网络请求;对于NetWork接口,Volley框架有一个默认的实现类,即BasicNetWork类。
BasicNetWork中有两个比较重要的属性:一个是HttpStack接口,是真正执行网络请求的接口;一个是ByteArrayPool对象,产生一个byte数组缓存响应内容。在我看来,BasicNetWork是HttpStack执行类的一个代理。它从performRequest方法可简单分为网络请求前处理、调用HttpStack的performRequest方法执行真正的网络请求、网络请求后的处理三部分。
网络请求前处理主要是指根据Request对象的缓存实体添加相应的请求参数;
HttpStack接口中的performRequest是真正执行网络请求的方法;Volley中有两个HttpStack接口的实现类,分别是HUrlStack和HttpClientStack,前者依赖HTTPUrlConnection进行网络请求,后者依赖HTTPClient进行网络请求;
网络请求后处理则是从原始HTTPResponse中提取响应码、响应头部参数以及响应内容;根据响应码判断该响应是否发生改变,以此来创出是否发生改变的NetWorkResponse对象。
HttpStack接口中的performRequest方法返回的是原始的HttpResponse对象;NetWork接口中的返回的是NetWorkResponse对象;网络线程中的一次逻辑处理流程则最终得到一个Response<T>对象。
至此,网络线程我们便大致分析完毕了;接着我们分析缓存线程。
二、缓存线程
Volley框架中只会开启一条缓存线程,来进行缓存逻辑业务的处理;与网络线程相似,缓存线程也有一个对应的线程类,那就是CacheDispatcher类。
与NetWorkDispatcher类相比,CacheDispatcher类逻辑会稍微复杂一点,但复杂得有限;CacheDispatcher类中也有四个比较重要的属性:一个缓存队列引用,一个Cache接口,一个ResponseDelivery接口以及一个网络队列引用。与NetWorkDispatcher相比,多了一个缓存队列的引用,少了一个NetWork网络请求接口。
在网络线程分析之中,我们并未太详细的介绍ResponseDelivery接口;而在缓存线程之中我们除了分析缓存线程逻辑处理的流程外,还要侧重于介绍ResponseDelivery接口。
与NetWorkDispatcher类似,CacheDispatcher中也只有一个重要的方法,那就是承载线程逻辑的run方法;同时缓存线程也是一个经典的线程模式:一个无限的while循环,一个决定是否退出无限循环的Boolean变量mQuit以及while循环之中的一次处理流程逻辑。
直接贴上源码:
public void run() {
...
//<strong>第一步</strong>
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();//初始化缓存
while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.<strong>第二步</strong>
final Request request = mCacheQueue.take();//获取请求
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {//取消<strong>第三步</strong>
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.<strong>第四步</strong>
Cache.Entry entry = mCache.get(request.getCacheKey());//获取缓存
if (entry == null) {//<strong>第五步</strong>
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);//无缓存,添加到网络请求队列之中
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {//缓存过期<strong>第六步</strong>
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);//添加到缓存之中
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
//解析缓存中的响应内容及响应头参数<strong>第七步</strong>
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
//传递响应<strong>第八步</strong>
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
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 {
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.停止线程的处理
...
}
}
}
缓存线程中一次逻辑处理流程并不复杂,但是却要比网络线程多一个步骤,一共有八个步骤:
1、在具体进行逻辑处理之前,将缓存线程的优先级设置为后台处理线程,并且初始化Cache接口;第一步大致与网络线程一致,即设置线程的优先级,不过与网络线程不同的是,缓存线程调用了Cache接口的initialize方法对Cache进行了初始化。至于Cache如何初始化,我们同样将在下一篇文章【进阶android】Volley源码分析——Volley的缓存一文中具体介绍;
2、从队列之中获取一个Request对象;其中这个队列是缓存队列,而网络线程中的队列是网络队列;
3、与网络线程的第三步一致,判断该Request对象是否已取消;
4、通过Request对象获取缓存Key;从Cache接口中获取Cache.Entry(缓存实体);在上一篇文章之中我们简单提及了一下Cache接口;Cache接口是一系列Cache.Entry的集合,该接口抽象了一列操作该集合的方法,例如缓存Cache.Entry,获取Cache.Entry等等,这一系列分析我们同样也将在下一篇文章【进阶android】Volley源码分析——Volley的缓存一文中具体介绍;
5、如果第四步获取的Cache.Entry为空,则表示该Request对象从来没有请求过网络,故而需要将该Request对象添加到网络请求队列之中,让网络线程对其进行网络请求;
6、如果Cache.Entry对象过期,则需要重新获取网络请求;因此也要如同第五步一样,将该Request对象添加到网络请求队列之中,并且将该Cache.Entry对象映射到Request对象之中;将Cahce.Entry与Request对象映射的作用是网络线程对该Request对象进行网络请求后发现得到的响应并未在服务端发生改变(响应码为304)时,直接通过该Request对象映射Cache.Entry来生成最终的Response<T>对象,而非通过原始的HttpResponse来生成;
7、直接通过Cache.Entry对象生成一个NetWorkResponse对象,并以此作为该Request对象中parseNetWorkResponse方法的入参,生成最终的Response<T>对象;
8、通过ResponseDelivery接口中的postResponse方法,将Request对象、Response对象作为入参传递给UI线程;如果生成的Response<T>需要刷新,则将响应传递给UI线程之后,还要讲该Request对象重新添加到网络请求队列之中,进行刷新。
通过以上八个步骤的梳理,我们也大致明白了缓存线程一次逻辑处理的流程了。
下面我们根据网络线程和缓存线程进行一下几点总结:
* 两个流程都是经典的线程处理逻辑,皆可停止;
* 两个流程中的NetWork、Request(抽象类)、Cache以及ResponseDelivery等不是接口就是抽象类,具有高度的抽象性;由此便可以看出Volley在设计模式上面的一些运用,从而间接说明了Volley框架的可扩展性;
* Request对象贯穿整个流程;无论是网络请求、解析响应、缓存处理还是与主线程交互都有此对象的参与;
* 响应的变化:
非304情况:HttpResponse(原始响应)——>NetWorkResponse(过渡产物)——>Response<T>(最终形式);
是304情况:Cache.Entry——>NetWorkResponse(过渡产物)——>Response<T>(最终形式)。
* 两个流程的相似点:生成NetWorkResponse对象,由此对象生成Response<T>对象,在传递给主线程;
* 两个流程的不同点:生成NetWorkResponse对象的来源不同,网络线程是来自于网络的原始HttpResponse,缓存线程是来自于缓存的Cache.Entry对象。
最后我们来分析一下ResponseDelivery接口; ResponseDelivery接口主要负责网络线程或者缓存线程与ui线程进行通信,以便UI线程能够通过网络请求的结果对界面进行一定的更新;与Cache、NetWork等接口相似,Volley框架也为ResponseDeliVery接口提供了一个默认的执行类,那就是 ExecutorDelivery类。ExecutorDelivery类中只有一个重要的属性:Executor,Executor是一个接口;ExecutorDelivery类通过构造器实例化一个Excutor的执行类,代码如下:
public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
//生成一个封装handler的Executor
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
通过此构造器我们可以得知,ExecutorDelivery类的本质还是通过Handler机制以消息的形式,向主线程传递一个回调(Runnable)。
下面我就看看ExecutorDelivery类是如何传递的:
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
很显然,ExecutorDelivery类传递给主线程的回调是一个ResponseDeliveryRunnable对象;该对象的构造函数中包含了Request对象和Response对象;如此当主线程在执行该回调时,就可以拿到Request对象和Response对象了。我们看看ResponseDeliveryRunnable对象的run方法,代码如下:
public void run() {
// If this request has canceled, finish it and don't deliver.
//如果请求取消了,调用finish方法
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// Deliver a normal response or error, depending
if (mResponse.isSuccess()) {
//处理响应结果,一般deliveryResponse方法之中都调用某个监听器的方法(监听器模式)
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) {
...
} else {
mRequest.finish("done");//请求最终完成
}
...
}
至此,Volley框架里的两种类型的线程我们就分析结束了。
当然由于本人自身水平所限,文章肯定有一些不对的地方,希望大家指出!