Volley 是谷歌推出的一款轻便的网络请求库,全部不足 50 个文件,与 OkHttp 相比实在算不上臃肿。使用上也比较方便,你可以自由地定义自己的 Request 类,直接将返回数据转换成自己需要的格式,如果你需要使用 ImageView 展示网络图片的话那就更巧了,Volley 提供了一个 NetworkImageView 类,只需要像设置本地图片文件一样将网络图片的 url 设置进去,它就会自动加载图片并展示。
轻便并不意味着功能的不丰富,从自定义 Request 到缓存,再到返回数据的分发,Volley 都提供了可供用户自定义功能的接口,只要你需要的功能没有那么苛刻,Volley 基本都能满足日常的网络访问需要。
同时,轻便意味着负载小,从发起网络请求,到收到返回数据,在 Volley 中走过的流程并不多,并且它内部还有一个轻量级的线程池,能够更有效地执行请求。
下面先从一个标准的网络请求过一遍 Volley 的代码。
请求流程
RequestQueue
使用 Volley 之前要先创建一个 RequestQueue 用于分发执行 Request ,创建 RequestQueue 实例有两种方法,一种是使用工厂类 Volley ,Volley 提供了四个方法用于创建 RequestQueue 实例,分别是:
publicstaticRequestQueuenewRequestQueue(Contextcontext);
publicstaticRequestQueuenewRequestQueue(Contextcontext, BaseHttpStackstack);
publicstaticRequestQueuenewRequestQueue(Contextcontext, HttpStackstack);复制代码
第二、三个方法的第二个参数分别是 BaseHttpStack 和 HttpStack ,它们的作用是执行具体的网络请求,用户可以继承/实现它们使用自己的网络请求功能,如可以内置使用 Okhttp 执行实际的网络请求,不过需要将 Okhttp 返回的 Resopnse 转换成 Volley 的 HttpResponse 。
两个类分别要实现的方法是:
// BaseHttpStack
publicabstractHttpResponseexecuteRequest(
Request<?>request, Map<String, String>additionalHeaders)
throwsIOException, AuthFailureError;
// HttpStack
org.apache.http.HttpResponseperformRequest(Request<?>request, Map<String, String>additionalHeaders)
throwsIOException, AuthFailureError;复制代码
目前 HttpStack 已经被标志为 Deprecated,原因是它使用的还是 org.apache.http.HttpResponse ,而最新的 BaseHttpStack 使用的则是 Volley 自己实现的 HttpResponse ,这里为了实现兼容,Volley 专门提供了两个类 BaseHttpStack 和 AdapterHttpStack 用于二者之间的转换,这里先按下不表。
再说 RequestQueue 实例的创建,除了使用 Volley 之外,用户也可以直接调用 RequestQueue 的构造方法,构造方法有三个重载,分别是:
publicRequestQueue(
Cachecache, Networknetwork, intthreadPoolSize, ResponseDeliverydelivery);
publicRequestQueue(Cachecache, Networknetwork, intthreadPoolSize);
publicRequestQueue(Cachecache, Networknetwork);复制代码
NetWork ,它声明了一个方法NetworkResponse performRequest(Request<?> request) throws VolleyError;
,用于从 Request 得到 NetworkResponse ,即网络请求的实现,Volley 有一个默认的实现 BasicNetwork ,即在使用 Volley 类创建 RequestQueue 时使用的类,但它也并不是真正的网络请求类,上面也说到真正的执行网络请求的是 HttpStack 和 BaseHttpStack,Volley 也有一个默认的实现,继承自 BaseHttpStack 的 HurlStack 和实现自 HttpStack 的 HttpClientStack 。
四个参数的意义都很明确,Cache 中声明了一些缓存相关的方法,在 Volley 进行网络请求的时候它会将自己认为需要缓存的请求使用 Cache 缓存,Cache 可以由用户自己实现,也可以使用 Volley 提供了一个默认 Cache ,DiskBaseCache ,该缓存会将数据保存在文件中,如果你觉得保存在文件中比较慢,也可以利用 LruCache 实现一个内存缓存类。
Cache,缓存类
Network,网络请求类
threadPoolSize,线程池大小
ResponseDelivery,Response 分发类
后者都是在增加了一个默认参数的情况下调用前者,所以只看第一个构造方法,它有四个参数:
threadPoolSize 就是线程池中存在的线程数,RequestQueue 之后会根据这个参数创建线程池。
ResponseDelivery ,用于分发 Response ,从 Volley 的默认实现来看,它可以用作线程切换,并对 Request 和 Response 做一些额外的处理,如将其加入缓存等。 创建实例之后,还需要再调用 RequestQueue 的start()
方法完成启动,方法实现如下:
/** Starts the dispatchers in this queue. */
publicvoidstart() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher=newCacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for(inti=0; i<mDispatchers.length; i++) {
NetworkDispatchernetworkDispatcher=
newNetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
mDispatchers[i] =networkDispatcher;
networkDispatcher.start();
}
}复制代码
这个方法创建了一个 CacheDispatcher 和 n 个 NetworkDispacher ,这里的 n 就是构造方法中传入的 threadPoolSize 。每一个 Dispatcher 都是一个小型的 Looper ,它们从 RequestQueue 中取 Request 并执行请求。用户调用add()
方法将一个 Request 放入 RequestQueue 中。在add()
方法中,RequestQueue 先判断 Request 是否可以缓存,然后将其加入到 mCacheQueue 或是 mNetworkQueue ,这两种不同的队列正是对应着start()
方法中初始化的两种 Dispatcher 。
Dispatcher
Volley 中共有两种 Dispatcher ,其中 NetwrokDispatcher 分发的是需要直接执行的请求,CacheDispatcher 分发的是可缓存的请求,换句话说,请求之前也需要检查是否已经存在缓存,缓存是否可用等。
它们有着共同的逻辑,都是继承于 Thread ,启动之后便不断调用processRequest()
方法从队列中取出 Request ,然后调用processRequest(Request)
处理 Request ,二者也就区别于此。
CacheDispatcher
在 CacheDispatcher 中,processRequest(Request)
会先根据 Request key 从 Cache 中查找有没有缓存,如果没有,则将其加入到 NetwordDispatcher 中等待处理,不过在加入 NetworkDispatcher 之前还需要经过一步判断,调用maybeAddToWaitingRequests(Request)
方法判断这个方法是否需要交给 NetworkDispatcher 处理。因为在某些情况下,CacheDispatcher 可能会短时间内同时处理两个相同的请求,但是这两个请求都没有缓存,所以要交给 NetworkDispatcher 处理,但是又不必都交给 NetworkDispatcher 处理,因为两个请求相同,第二个请求只需要复用第一个请求的结果即可,这个判断过程就是maybeAddToWaitingRequests(Request)
要执行的,它会将后续的请求放到暂存到一个列表中,并监听第一个 Request 的请求情况,当请求完成的时候,就会再将结果分发给这些相同的 Request。
总结来说,就是 CacheDispatcher 会优先从查找缓存,如果没有,就会将请求交给 NetworkDispatcher ,并且会截断重复请求。
NetworkDispatcher
NetworkDispatcher 与 CacheDispatcher 有着类似的逻辑,不同之处在于processRequest(Request)
方法会调用 Network 完成网络请求,方法内部的执行大概分为三步:
判断 Request 的有效性
调用 Network 完成网络请求,并利用 Request 将 NetworkResponse 转换成 Response<T>
将 Request 和 Response 分发出去,并在需要缓存的时候将其加入缓存
分发的时候会调用 ResponseDelivery 的postResponse()
方法,这个方法的默认实现就是使用主线程的 Handler 将线程切换到主线程。
Request & Response
RequestQueue 提供了网络请求的一些功能,但具体的数据还是由 Request 和 Response 持有。
Request
Request 是一个抽象类,共有两个抽象方法:
/**
* Subclasses must implement this to parse the raw network response and return an appropriate
* response type. This method will be called from a worker thread. The response will not be
* delivered if you return null.
*
* @param response Response from the network
* @return The parsed response, or null in the case of an error
*/
protectedabstractResponse<T>parseNetworkResponse(NetworkResponseresponse);
/**
* Subclasses must implement this to perform delivery of the parsed response to their listeners.
* The given response is guaranteed to be non-null; responses that fail to parse are not
* delivered.
*
* @param response The parsed response returned by {@link
* #parseNetworkResponse(NetworkResponse)}
*/
protectedabstractvoiddeliverResponse(Tresponse);复制代码
parseNetworkReponse()
负责将 NetwrokResponse 转换成 Response<T>,deliverResponse()
则负责将最终的数据分发出去。
除此之外 Request 里就存有一些请求参数、优先级以及 URL 和请求方法等。
要使用 Request 就需要一个继承 Request 的子类,在 Volley 中内置了四种 Request ,分别是 StringRequest、JsonRequest、ImageRequest 和 ClearCacheRequest 。前三者就是常规性的格式转换 Request ,能够分别将请求到的原始数据转换为字符串、Json对象或 Bitmap ,以 StringRequest 为例,它的parseNetworkResponse()
的实现如下:
protectedResponse<String>parseNetworkResponse(NetworkResponseresponse) {
Stringparsed;
try{
parsed=newString(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch(UnsupportedEncodingExceptione) {
// Since minSdkVersion = 8, we can't call
// new String(response.data, Charset.defaultCharset())
// So suppress the warning instead.
parsed=newString(response.data);
}
returnResponse.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}复制代码
而最后一个是功能性 Request ,它的作用就是清空缓存。
publicbooleanisCanceled() {
// This is a little bit of a hack, but hey, why not.
mCache.clear();
if(mCallback!=null) {
Handlerhandler=newHandler(Looper.getMainLooper());
handler.postAtFrontOfQueue(mCallback);
}
returntrue;
}复制代码
在一定会调用的方法isCanceled()
中调用了 Cache 的clear()
方法,实现清空缓存,但大部分时候并不需要这么麻烦,直接调用 Cache 的方法即可,考虑过是否是为了使对 Cache 的操作都处于一个线程中,但是在 Volley 中,默认的 BasicDiskCache 是线程安全的,而对其的操作也是分布在 CacheDispatcher 和 NetwrokDispatcher 中的(它们分别在不同的线程),所以抛去了这种考虑,那就实在想不出 ClearCacheRequest 的存在有什么特殊的意义了。
Response
Response 对应的类在 Volley 中共有两个,分别是 NetworkResponse 和 Response<T>,前者对应着网络请求得到的实际响应,调用Network.performRequest()
之后得到,然后再由Request.parseNetworkResponse()
对其进行格式转换,一般需要与 Request 结合使用,每一个自定义的 Request 用于解析一种数据类型,如 StringRequest 对应 String 类型,同样,与之对应的 Response 返回的类型也是 String ,二者在 StringRequest 的构造方法中统一:
publicStringRequest(
intmethod,
Stringurl,
Listener<String>listener,
@NullableErrorListenererrorListener);复制代码
此处的 Listener 就是用户设置的监听器,当请求完成之后,会回调Listener.onResponse()
方法,将结果传递给调用方,且此时已经过 ResponseDelivery 处理,会在用户设置的线程中回调。
轻便性
Volley 的轻便性,我认为体现在这几个方面。
NetworkImageView
首先,Volley 为网络照片的展示提供了十分便利的方式——一个自定义的 NetworkImageView ,为了实现这个 ImageView ,它还提供了相关的请求类,如 ImageLoader 、ImageRequest 等,利用这三个类,我们可以比较轻松的完成普通的网络图片的加载,当然,它本质上来说只是一个网络请求库,无法提供像 Glide 那样丰富的图片加载功能,但是 Volley 胜在简单方便。
Request
其次,Volley 的 Request 这个特点让我们处理某种特定类型的数据变得十分简单,当我们实现了一个 XXXRequest ,它能够十分方便的工作,就像它 Request 原本就是为此工作的一样。
代码轻量级
最后就是,Volley 这个库本身的实现很简单,总共不过40多个文件,却构成了一个完整的网络请求库,不仅如此,它也没有拉下该有的功能,当我们想快速实现某个功能的时候,它给所有组件都提供了默认的实现,当我们想细化某一功能的时候,我们也可以使用自定义的组件实现某个功能。
另一方便,对于一个想了解 Volley 内部实现过程的开发者来说也是十分友好的,Volley 有比较少的类,同时条理也比较清晰,在 Volley 中没有过多的设计。
可扩展性
对于 Volley 的可扩展性,及用户的自定义上来说,可以从以下几个接口一窥究竟。
Cache
首先,Cache 是一个接口,定义了诸如get()
、put()
等方法,这个类 Volley 基于文件读写实现了一个默认的 BasicDiskCache 类。Cache 会在 RequestQueue 的构造方法中传进系统,并在之后传递给 CacheDispatcher 和 NetworkDispatcher ,所以,当这个默认的缓存不满足需要时,我们完全可以自定义一个缓存类替换掉这个默认的,如实现一个内存缓存类,或者更有甚者,我们可以在get()
方法中截断某些 Request 的网络请求,返回一个由 Cache 自己生成的响应结果。
Network
Network 只有一个方法,即根据 Request 进行网络请求,得到 NetworkResponse ,这个类的实例也是在 RequestQueue 的构造方法中传入,与 Cache 一样,Network 有自己的实现,但是我们同样可以使用自定义的 Network 完成网络的加载,如在某些情况下可能要对 Request 进行额外的处理之后再进行网络请求,如给所有 Request 加一个请求头字段,便可以在自定义类的performRequest()
方法中对 Request 进行处理,不仅如此,如果觉得 Volley 提供的默认的网络请求功能有效率或者其他方面的问题,还可以在这个方法中使用其他的库进行加载,比如可以在里面再套一层 Okhttp ,但是这样使用的问题就是要先把 Volley 的 Request 类转换成 Okhttp 的 Request 类,并将 Okhttp 的 Response 再转回 NetworkResponse 。
threadPoolSize
NetworkDispatcher 的数量,即同时在 Volley 中进行网络请求的线程数量,也就是简化的线程池,这样便可以充分利用多线程的优势,更快地完成网络请求。用户可以设置不同的数量以满足不同的需求,但是 threadPoolSize 的最小值是 1,也就是说任何时候,Volley 中都至少有两个线程在工作(还有一个 CacheDispatcher 所在的线程)。
ResponseDelivery
这是一个接口,拥有三个方法,定义如下:
/** Parses a response from the network or cache and delivers it. */
voidpostResponse(Request<?>request, Response<?>response);
/**
* Parses a response from the network or cache and delivers it. The provided Runnable will be
* executed after delivery.
*/
voidpostResponse(Request<?>request, Response<?>response, Runnablerunnable);
/** Posts an error for the given request. */
voidpostError(Request<?>request, VolleyErrorerror);复制代码
Request 是一个抽象类,这也就意味着它提供了一定的扩展功能,这在上文也提到过,用户可以自定义自己的 Request 用于将原始数据解析成特定的格式。大部分时候,我们调用的 API 都返回某种格式的 json 数据,在网络请求完成之后我们往往会先调用一个方法解析数据,而在使用 Volley 的时候,就不需要再有这额外的操作了,直接定义一个 Request ,重写它的parseNetworkResponse()
方法,那么返回的 Response 数据便会是我们需要的格式。
Request & Response
按照这个逻辑,用户可以轻松地替换掉默认的 ResponseDelivery ,自己实现一个能够切换到更多线程的功能,如 RxJava 中,可以切换到所在线程、主线程或者是其他的子线程等。
这些方法会在 CacheDispatcher 和 NetworkDispatcher 得到结果之后调用,调用所在的线程是它们所在的子线程,所以为了满足回到主线程工作的需要,ResponseDelivery 的默认实现 ExecutorDelivery 所做的事情就是将线程切换到主线程,利用的是一个主线程的 Handler ,向主线程提交了一个 Runnable ,在这个 Runnable 中,Request 会调用自己的deliverXXX()
方法将结果回调给自己的 Listener ,完成整个网络请求。
兼容性
Volley 中除了以上的这些之外,还有一个比较独特的地方,即 HttpStack 和 BaseHttpStack 这几个执行实际网络请求的类上面。下面一一列出:
HttpStack
接口,声明了一个网络请求方法performRequest()
,它的返回参数是 org.apache.http.HttpResponse,已被标注弃用。
BaseHttpStack
抽象类,实现了 HttpStack 接口,并增加了一个新的方法executeRequest()
,它的返回参数是 HttpResponse 。
它实现了 HttpStack 的performRequest()
方法,但是在这个方法内部实际是调用executeRequest()
方法获取到了 HttpResponse 实例,然后再将其转换为 org.apache.http.HttpResponse 返回。
AdapterHttpStack
继承自 BaseHttpStack ,实例化时需要一个 HttpStack 实例,然后在实现executeRequest()
方法的时候使用了performRequest()
方法得到 org.apache.http.HttpResponse 实例,然后将其转换为 HttpResponse 返回。
HurlStack
继承自 BaseHttpStack ,使用 HttpUrlConnection 实现了executeRequest()
方法。
HttpClientStack
实现了 HttpStack 接口,使用 HttpClient 实现了performRequest()
方法,已被标注弃用。
通览整个 HttpStack 家族的五个类,你是否明白了它们的工作原理?首先从两个实现类 HurlStack 和 HttpClientStack 说起,它们分别使用了 HttpUrlConnection 和 HtttClient 实现,这两个类都是 Android 原生提供的网络请求类,但是它们适用于不同的 Android 版本,在 5.1 版本之后,HttpClient 便被 Google 弃用,并推荐使用 HttpUrlConnection 。所以 Volley 在使用这两个类的时候也做了版本上的兼容。
最开始 Volley 仅提供了一个 HttpStack ,这个类默认就是使用 HttpClient 进行网络请求,但是在之后的版本中 HttpClient 被弃用,所以又出现了 BaseHttpStack ,它提供了一个不需要依赖 HttpClient 的方法executeRequest()
,但是它并不能避免用户继续调用performRequest()
方法,所以重写了这个方法在内部调用executeRequest()
,但是还有一个问题,如果用户一直使用的都是自定义的 HttpStack 类,那在重新使用 BaseHttpStack 的时候必然还要重新实现一遍,所以,又有了 AdapterHttpStack 的存在,它继承自 BaseHttpStack ,构造方法中传入了 HttpStack 实例,但是与 BaseHttpStack 相反,它内部的逻辑是exetureRequest()
的实现依赖于performRequest()
的实现,用户可以将自己原本实现了 HttpStack 的类实例作为参数传递给 AdapterHttpStack ,便可以得到一个可工作的 BaseHttpStack 实例了。