【进阶android】Volley源码分析——Volley的线程
来源:互联网 发布:域名备案有什么用 编辑:程序博客网 时间:2024/05/16 07:51
在上一篇文章中,我们主要分析了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框架里的两种类型的线程我们就分析结束了。
当然由于本人自身水平所限,文章肯定有一些不对的地方,希望大家指出!
- 【进阶android】Volley源码分析——Volley的线程
- 【进阶android】Volley源码分析——Volley的流程
- 【进阶android】Volley源码分析——Volley的缓存
- 【进阶android】Volley源码分析——Volley的工具【StringRequest】
- 【进阶android】Volley源码分析——Volley的工具【ImageLoader】
- 【进阶android】Volley源码分析——总述
- android Volley的源码分析
- Android——volley源码分析
- Android-Volley源码分析
- [Android]volley源码分析
- Android-Volley源码分析
- android Volley 源码分析
- Volley的源码分析
- android-----Volley框架源码分析
- android-----Volley框架源码分析
- Android Volley源码分析(1)
- Android Volley源码分析(2)
- 【Android框架】volley源码分析
- ISE中下载Xilinx的bit文件失败时的处理方案
- 平平淡淡才是真 安安乐乐才是福
- 客户端开发--2控制器开发准备(1)【界面布局和行为控制】
- 移动广告名词
- 面向对象的三大基本特征
- 【进阶android】Volley源码分析——Volley的线程
- CString中Format函数与格式输入与输出
- UVA 10420(排序检索)
- hdoj 1865 1sting
- Service在前台运行
- iOS-UI-01 UIWindow UIView
- 将对话框(窗口)设置成无边框无标题栏样式
- AngularJS 之 Factory vs Service vs Provider
- HDOJ1022(栈)