前面三篇文章从最基础的TCP,HTTP协议理论开始,然后介绍了在Android的开发中所使用的HttpClient和HttpUrlConnection这两种Http客户端。在本文中,我们一起来学习一下在Android开发中经常使用的volley框架。首先,我们会从架构的角度了解一下整个框架的结构,然后从源码的角度理解框架实现细节。
volley是Google在13年发布的一款Android异步网络请求框架。volley有着鲜明的特点:适用于小数据量且频繁的网络请求。这个特点特别适合于Android应用程序的网络操作。另外,我们从接下来将要介绍的架构中可以发现,volley采用了大量面向接口的设计,保证了整个框架的开放性和灵活性,可以根据不同的情况需求进行定制。同时,volley也提供了简单的图片加载工具。
在我们研究volley框架的结构之前,我们不妨自己先来思考一下如何实现一款网络操作的框架。首先,我们根据实际需求设计网络操作请求(无非就是URL及其参数),然后使用多线程并发来执行和处理网络操作请求任务。从服务器中取到数据后,将数据放在内存的cache中以便应用程序使用。
其实volley框架也是如此设计的,如下图所示:
从图中我们可以看到,整个框架的结构分为4个部分:
1、volley使用一个请求队列来管理各种网络请求Request。Request本身是一个描述请求的抽象类,我们可以根据具体需求情况实现该抽象类。volley也提供了一些Request子类,例如StringRequest,JsonRequest,ImageRequest等等。
2、在请求队列RequestQueue中,有两种个轮询分发线程负责对请求任务进行分发调度。第一种是CacheDispatcher,负责调度数据保存在cache中的请求任务;第二种是NetworkDispatcher,负责调度数据在远端服务器上的请求任务。另外,RequestQueue中还有一个叫作ResponseDelivery的接口,用于进行结果分发。请求队列中的网络请求会首先被放入Cache任务队列中,被CacheDispatcher线程调度。CacheDispatcher会试图从cache中取出该任务所请求的数据,如果命中则交给ResponseDelivery解析该数据并返回给应用程序;如果未命中,或者缓存失效等情况下,则将该请求任务加入到网络任务的队列中,供NetworkDispatcher进程调度。NetworkDispatcher请求结束则将结果交给ResponseDelivery作后续的处理。
3、从上面的分析中我们可以看出,我们所请求的数据主要存在两个个地方:Cache和网络。volley中分别使用Cache和HttpStack这两个接口来描述它们以及所需执行的操作。其中,HttpStack负责处理http网络请求,volley中有两种方式实现了HttpStack接口:基于 HttpURLConnection 的HurlStack 和 基于HttpClient 的HttpClientStack 。而Cache既可是基于SD卡,又可以基于内存。
4、通过上面所述的两个接口可以获取并操作我们请求的数据,这些数据主要分布在网络服务器和本地内存或SD卡中。
volley维护了一个请求队列来管理应用程序的网络请求,并采用了单例模式来保证一个应用程序只含有一个请求队列。通常情况下,我们会继承Application类,并通过newRequestQueue方法来创建一个请求队列。从源码中我们可以看出,在Android2.3以上使用了基于HttpUrlConnection的HurlStack处理网络请求,而2.3以下使用了基于httpclient的HttpClientStack来处理网络请求。这里的原因在上一篇文章中我们提到过,这里不再赘述。下面我们顺着请求的提交—>处理—>完成 的这条线路来研究一下框架的内部细节。
针对不同的网络请求,我们可以实现Request这个抽象类。该抽象类描述了请求的url,方式,head,body以及优先级等等信息。然而volley已经为我们实现了大部分的子类来满足各种需求。在Request的子类中,我们需要重写两个方法:
protected Response<T> parseNetworkResponse(NetworkResponse response) :用于将网络返回的字节流解析为合适的数据类型。 protected void deliverResponse(T response) :将解析好的数据传递给它的监听回调。
另外,如果我们自定义Request,通常也会重写getBody()方法来构建body内容;如果并未重写getBody方法,那么将会把getParams()方法返回的K-V值拼接起来的字节码作为body。
定义好了请求Request,接下来我们来通过源码来研究一下RequestQueue这个类。RequestQueue作为volley框架的核心类,负责管理应用程序的网络请求。我们在使用volley进行网络的时候,向请求队列提交了Request后发生了什么呢?请看下图。
RequestQueue使用一个set来存储一个未处理的请求。当我们提交一个请求后,RequestQueue会将该请求加入到这个集合中:
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
我们在上面已经提到过,我们主要从cache和网络来请求数据。于是,在RequestQueue中维护了两个请求队列:cache请求队列CacheQueue和网络请求队列NetworkQueue:
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>(); private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();
我们可以看到,这两个队列采用了优先阻塞队列PriorityBlockingQueue来维护请求。每个新请求都会先放入cache队列等待调度,只有在cache未命中或无效的情况下会被放入网络请求队列。但是,如果一个请求在处理的同时,又有相同url的请求怎么办呢?显然重复的请求只要到cache中去取就好了,无需再次进行网络请求,所以volley采用一个map来管理重复的请求,将它们暂时放入map中等待:
private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>();
提交了请求之后,接下来的工作就是对请求进行处理了,接下来我们来研究一下volley的调度策略。
对应于两种请求队列,分别使用CacheDispatcher和NetworkDispatcher两个线程来调度分发,上面已经介绍过这两种线程了,来看一下RequestQueue初始化和启动的代码:
public void start() { stop(); // Make sure any currently running dispatchers are stopped. // Create the cache dispatcher and start it. mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); mCacheDispatcher.start(); // Create network dispatchers (and corresponding threads) up to the pool size. for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } }
从源码中我们可以看出RequestQueue启动了一个CacheDispatcher线程和多个NetworkDispatcher线程来完成调度分发,下面我们来研究一下这两种线程。
CacheDispatcher
CacheDispatcher启动后会不断地轮询mCacheQueue,队列为空时则等待;如果请求的cache未命中,过期或者无效,则会把该请求加入到mNetworkQueue中。当请求处理完成后则会将结果交给ResponseDelivery做最后的处理。具体流程如下:
NetworkDispatcher
NetworkDispatcher不断轮询mNetworkQueue取出请求去执行,队列为空则等待,请求处理结束则将结果传递给 ResponseDelivery 去执行后续处理,并判断结果是否要进行缓存。具体流程如下:
从图中可以看到,无论是CacheDispatcher还是NetworkDispatcher,请求结束后都会将结果交给ResponseDelivery这个接口来做后续处理,在ResponseDelivery中主要有三个方法还传递请求结果或者错误:
public void postResponse(Request<?> request, Response<?> response) // 用于传递请求结果, request 和 response 参数分别表示请求信息和返回结果信息。 public void postResponse(Request<?> request, Response<?> response, Runnable runnable) //用于传递请求结果,并在完成传递后执行 Runnable public void postError(Request<?> request, VolleyError error); //用于传输请求错误
当一个请求处理完成后,首先需要将当前处理集合mCurrentRequests 中的请求移除,然后在重复请求的mWaitingRequests中查询是否有正在等待的重复请求,如果有则放入缓存队列中处理。
总结
通过从请求的创建—>提交—>处理—>完成 这条主线,本文简单的介绍了volley框架的执行流程以及部分细节。volley的整个框架采用了大量的面向接口的设计,保证了使用的灵活性和框架的开放性。同时,volley又实现了很多接口来帮助开发者应对各种需求,在保证灵活性的同时也减少了很多繁琐的工作。