概述
Volley是一个HTTP库,它能够帮助Android应用更方便地执行网络操作,更重要的是,它更快速高效。
Volley有如下优点:
- 自动调度网络请求
- 高并发网络连接
- 具有标准HTTP缓存一致性的透明磁盘和内存缓存
- 支持请求优先级
- 提供取消请求的API,可以取消单个或多个请求。
- 易于定制,例如重试和回退功能
- 强大的指令使得从异步网络加载的数据正确地显示到UI上的操作变得更加容易
- 包含调试与追踪工具
Volley的特点:适合数据量小,通信频繁的网络操作。
请求过程
Volley的请求过程如官网提供的演示图所示,简单来说就几下几个步骤:
- 新建一个
RequestQueue
对象,并启动 - 新建Request对象,通过
add()
方法,先将请求添加到缓存队列中 - 缓存调度器从缓存队列中取出一个请求,并在缓存中检索该请求,如果缓存中已有该请求,且请求未过期,则解析请求结果,并通过
ResponseDelivery
将解析后的结果分发给主线程 - 如果缓存中找不到该请求或请求过期,则将请求添加到网络请求队列中
- 网络请求调度器从网络请求队列中取出一个请求,通过网络接口获取请求结果。如果获取成功,则解析请求结果,并将结果分发给主线程;如果发生异常,则发送请求失败的通知
代码解析
RequestQueue
RequestQueue
是一个带有调度线程池的请求调度队列,可以通过add()
方法添加一个请求到队列中,然后通过缓存或者网络方式获取数据,再分发一个解析过的响应到主线程。
先来看下RequestQueue的一些主要参数:
-
mCurrentRequests
:Set<Request<?>>,存储了所有待处理的请求。 -
mCacheQueue
:PriorityBlockingQueue<Request<?>>,缓存队列 -
mNetworkQueue
:PriorityBlockingQueue<Request<?>>,网络请求队列 -
mDispatchers
:NetworksDispatcher[],网络请求调度器,是个数组 -
mCacheDispatcher
:CacheDispatcher,缓存调度器 -
mDelivery
:ResponseDelivery,用于分发请求结果 -
mNetwork
:处理HTTP请求的网络接口 -
PriorityBlockingQueue
:优先级队列,不支持null元素,存储对象必须实现Comparable
接口,因为需要通过每个元素的compareTo()
方法对元素进行排序。
我们都知道volley的可定制性很高,具体表现在mCache
、mNetwork
、mDelivery
均可以自定义,只需要实现相应的接口即可,网络请求调度器的线程数量也可以自定义(默认为4)。
创建RequestQueue对象
首先,我们知道要使用volley来进行网络操作,需要创建一个RequestQueue
对象,最简单的方法就是通过Volley工具类来new一个:
RequestQueue requestQueue = Volley.newRequestQueue(context);
看下Volley类的代码实现:
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, (BaseHttpStack) null);
}
public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
BasicNetwork netwokr;
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
network = new BasicNetwork(new HurlStack());
} else {
...
}
} else {
network = new BasicNetwork(stack);
}
return newRequestQueue(context, network);
}
private static RequestQueue newRequestQueue(Context context, Network network) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
可以看到,最终调用的是newRequestQueue(Context, Network)
这个方法,这个方法,就做了两件事:
- new一个
RequestQueue
对象:初始化Cache
,Network
,NetworkDispatcher[]
,Delivery
。 - 启动
RequestQueue
: queue.start()
启动RequestQueue
通过RequestQueue
的start()
可以启动请求队列,看下启动都做了什么:
public void start() {
stop(); // 停掉正在运行的调度器
// 初始化缓存调度器线程,并启动
mCacheDispatcher = new CachePatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// 根据指定的线程池大小(默认4),始化n个网络调度器线程,添加到网络调度器数组中,并启动它们
for (int i = 0; i < mDispatcher.length; i++) {
NetworkDispatcher networkDispatcher =
new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
mDispatcher[i] = networkDispatcher;
networkDispatcher.start();
}
}
通过以上代码,可以看出,启动RequestQueue
,其实就是启动缓存调度器和网络调度器。其中,缓存调度器只有一个,而网络调度器有多个,根据创建RequestQueue
对象时传入的threadPoolSize
参数,未传入的话则使用默认值:
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
到这里,RequestQueue
就开始工作了,可以通过调用add(Request)
发送请求了。
public <T> Request<T> add(Request<T> request) {
request.setRequestQueue(this);
// 将request添加到mCurrentRequests集合中
synchroinzed (mCurrentRequests) {
mCurrentRequests.add(request);
}
// 按添加的顺序处理请求
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// 如果请求是不可缓存的,跳过缓存队列,直接发到网络请求队列
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
mCacheQueue.add(request);
return request;
}
添加请求到队列中时,首先会添加到mCurrentRequests
集合中,这个集合存放了所有未处理的请求。接着判断请求是否是可缓存的,如果不可缓存,直接添加到网络请求队列,否则会添加到缓存队列,其中请求的shouldCache
默认为true
,需显示设为false
,否则请求都会先发到缓存队列中。
Request
Volley根据常用请求方式,实现了以下种Request
:
- StringRequest
- ImageRequest
- JsonRequest:
- JsonArrayRequest
- JsonObjectRequest
用户也可根据自己的需求,自定义Request
,必须实现以下两个方法:
-
parseNetworkResponse(NetworkResponse response)
:对请求结果进行解析,将NetworkResponse
解析成合适的类型 -
deliverReponse(T response)
:将解析后的请求结果分发给监听器,请求结果不能为null
,解析失败的请求也不能进行分发。
CacheDispatcher
在上一节中我们知道,默认情况下,一个请求会优先发送到缓存队列中,由缓存调度器处理,而缓存调度器就是一个Thread
,我们来看下它的run()
方法做了哪些事情:
@Overrid
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mCache.initialize();
// 开始死循环
while (true) {
try {
// 处理请求
processRequest();
} catch (InterruptedException e) {
// 通过mQuit标志判断是否中断线程
if (mQuit) {
Thread.currentThread().interrupt();
return;
}
}
}
}
真正处理请求的地方在processRequest()
方法,接着往下看:
private void processRequest() throws InterruptedException {
// 从缓存队列中取出一个请求
final Request<?> request = mChacheQueue.take();
processRequests(request);
}
void processRequest(final Request<?> request) throws InterruptedException {
request.addMarker("cache-queue-take");
// 如果请求已经取消,不做调度
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
return;
}
// 尝试从缓存中检索该请求
Cache.Entry entry = mCache.get(request.getCacheKey());
// 缓存中未找到该请求
if (entry == null) {
request.addMarker("cache-miss");
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
// 缓存中的请求过期
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
request.addMarker("cache-hit");
Response<?> response =
request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// 请求结果是最新的,直接通过分发器post请求结果
mDelivery.postResponse(request, response);
} else {
// 请求结果不新鲜,重新获取
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
response.intermediate = true;
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mDelivery.postResponse(request, response,
new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
} else {
mDelivery.postResponse(request, response);
}
}
}
代码比较长,但逻辑很清晰.首先,会判断该请求是否已经取消了,如果是的话,直接返回,不再对该请求进行处理。
如果请求未取消,就去尝试从缓存中检索该请求,检索结果分为以下几种情况:
- 检索失败:发送到网络请求队列
- 检索到请求,但已过期:发送到网络请求队列
-
检索到请求,且未过期,解析响应数据,这里又分为以下几种情况:
- 响应已过期,需要更新数据:发送到网络请求队列更新数据,然后将缓存数据分发给线程
- 响应未过期:分发给主线程
如果检索失败或过期,需要将请求添加到网络请求队列中,但并不是直接添加到mNetworkQueue
中,而是调用mWaitingRequestManager.maybeAddToWaitingRequests(request)
方法进行判断。
这个WaitingRequestManager
的作用是防止重复请求的,来看下它的实现:
private final Map<String, List<Request<?>>> mWaitingRequests = new HashMap<>();
private synchronized boolean maybeAddToWaitingRequests(Request<?> request) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// 等待队列中包含该请求,即已经有请求在执行了,
// 将其加入等待队列中,不再发送到网络请求队列中
List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new ArrayList<>();
}
request.addMarker("waiting-for-response");
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
return true;
} else {
// 当前没有同样cacheKey的请求在执行,可以发送到网络请求队列中
mWaitingRequests.put(cacheKey, null);
// 监听网络请求结束
request.setNetworkRequestCompleteListener(this);
return false;
}
}
WaitingRequestManager
维护了一个cacheKey
到请求列表的映射表mWaitingRequests
,如果请求不在等待队列中,就将其加入映射表,然后返回false
,表示可以将这个请求直接添加到mNetworkQueue
中。相反,如果映射表中存在该请求了,表示已经有相同cacheKey
的请求已经在执行了,避免重复操作,将其加入等待队列,返回true
,不再添加到mNetworkQueue
。
这里还注册了一个网络请求结束的监听器,接受到请求响应后,会将cacheKey
对应的等待队列从映射表中移除,并分发响应结果。
NetworkDispatcher
接下来我们来看NetworkDispatcher
的工作流程,它的run()
方法与CacheDispatcher
一样,真正的操作都是放在processRequest()
方法中执行,所以我们直接看processRequest()
方法。
// 与CacheDispatcher一样,从队列中取出请求,不过这里的队列是网络请求队列
private void processRequest() throws InterruptedException {
final Request<?> request = mChacheQueue.take();
processRequests(request);
}
private void processRequest(Request<?> request) throws InterruptedException {
long startTimeMs = SystemClock.elapsedRealtime();
try {
request.addMarker("network-queue-take");
if (request.isCanceled()) {
// 请求已取消,不做处理
request.finis("network-discard-cancelled");
request.notifyListenerResponseNotUsable();
return;
}
addTrafficStatsTag(request);
// 调用网络接口处理请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 如果服务器返回304且我们已经分发了一个响应,不再分发第二个相同的响应
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finis("not-modified");
request.notifyListenerResponseNotUsable();
return;
}
// 解析响应,自定义的Request必须实现该接口
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// 如果请求需要缓存,缓存响应
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// 分发响应
request.markDelivered();
mDelivery.postResponse(request, response);
request.notifyListenerResponseReceived(response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
request.notifyListenerResponseNotUsable();
} catch (Exception e) {
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
request.notifyListenerResponseNotUsable();
}
}
与CacheDispatche
r一样,NetworkDispatcher
也会先判断请求是否取消,如果是的话,就停止处理该请求。
如果请求未取消,就调用网络请求接口去处理请求并获取响应。
获取到响应之后,就将获取到的NetworkResponse
对象解析成可用于分发的Response
对象。
此时如果请求需要缓存,就将其存到缓存中。
之后就开始分发请求。
注意到这里的两个调用:
request.notifyListenerResponseNotUsable
request.notifyListenerResponseReceived
这两个调用是用来通知注册了NetworkRequestCompleteListener
监听器的对象网络请求的结果,上面的WaitingRequestManager
就实现了该监听器,并在将请求添加到等待队列中时将监听器注册到请求中,以便在网络请求结束时作出相应的响应。
到这里,请求调度的部分就讲完了,接下来我们来看看volley是怎么将请求结果分发到主线程的。
ResponseDelivery
ResponseDelivery
是一个接口,定义了三个方法:
-
postResponse(Request<?> request, Response<?> response)
:分发请求结果 -
postResponse(Request<?> request, Response<?> response, Runnable runnable)
:分发请求结果,它提供了一个Runnable参数,将在分发后执行 -
postError(Request<?> request, VolleyError error)
:网络请求出现异常时
Volley实现了该接口:ExecutorDelivery
,也可以自定义分发器,在创建RequestQueue
的时候传入自定义分发器的对象。
ExecutorDelivery
ResponseDelivery
接口的实现类。主要实现就是通过Handler
机制来进行分发。
来看下它的实现:
private final Executor mResponsePoster;
public ExecutorDelivery(final Handler handler) {
mResponsePoster =
new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
@Override
public void postResponse(Request<?> reqeust, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
可以看到,ExecutorDelivery
只是创建了一个Executor
来封装Handler
,分发的时候新建一个ResponseDeliveryRunnable
对象,再通过Handler
发送该Runnable
到消息队列中。ResponseDeliveryRunnable
是ExecutorDelivery
的子类,它实现了Runnable
接口,我们来看下它做了哪些事情。
private static class ResponseDeliveryRunnable implements Runnable {
private final Request mRequest;
private final Response mResponse;
private final Runnable mRunnable;
...
@Override
public void run() {
// 同样的,在分发过程中,如果请求被取消了,也将不会被分发
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// 请求成功,分发请求结果;请求失败则分发失败内容
if (mResponse.isSuccess()) {
mReqeust.deliveryResponse(mResponse.result);
} else {
mRequest.deliveryError(mResponse.error);
}
// 如果该请求结果只是中间产物,表示请求还未完成,为其添加标记;
// 否则表示请求完成,调用finish()方法
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mReqeust.finish("done");
}
// 如果Runnable对象不为空,执行它
if (mRunnable != null) {
mRunnable.run();
}
}
}
可以看出,ExecutorDelivery
的分发过程如下:
- 判断请求是否已取消:
- 已取消:停止分发
- 未取消:继续下一步
- 请求未取消,判断请求是否成功
- 请求成功:发送请求结果
- 请求失败:发送失败相关内容
- 判断请求是否中间产物:
- 是中间产物:表示请求还未结束,添加标记
- 不是中间产物:该请求已结束,调用finish()方法
- 如果提供了
Runnable
对象,就执行它。
请求结束
我们来看下请求结束后,volley会做什么事情,先看下Request
的finish()
方法
void finish(final String tag) {
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
...
}
回顾之前调用Request
的add()
方法添加请求的时候,我们会将RequestQueue
对象绑定到Request
上,这里就会调用RequestQueue
的finish()
方法:
<T> void finish(Request<T> request) {
// 从mCurrentRequests集合中移除已结束的请求
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
// 通知所有RequestFinishedListener监听器请求结束
synchronized (mFinishedListeners) {
for (RequestFinishedListener<T> listener : mFinishedListeners) {
listener.onRequestFinished(request);
}
}
}
到这里,一个请求的处理就结束了。
Volley对图片请求也做了一定支持,它提供了一个ImageLoader
来负责图片的请求。接下来,我们看看ImageLoader
是怎么工作的。
ImageLoader
首先看下简单用法
mImageLoade = new ImageLoader(mRequestQueue, mBitmapCache);
mImageLoader.get(IMG_URL, ImageLoader.getImageListener(mImageView, R.drawable.ic_default_img,
R.drawable.ic_error_img));
首先,看下ImageLoader
的构造函数:
public ImageLoader(RequestQueue queue, ImageCache imageCache) {
mRequestQueue = queue;
mCache = imageCache;
}
首先,需要传入ImageQueue
的对象,由此可以推测ImageLoader
也是基于ImageQueue
来做请求分发的,只是针对图片请求做了一个封装。ImageCache
是图像缓存接口,可以避免不断重复获取图片,需要开发者自己实现ImageCache
接口,官方推荐使用LruCache
来做图像缓存,此处不做赘述。
接着我们通过ImageLoader
的get()
方法,可以轻松获取网络图片,来看下它的参数:
-
requestUrl
:网络图片的url
-
imageListener
:网络图片获取的监听器 -
maxWidth
:返回图片的最大宽度 -
maxHeight
:返回图片的最大高度 -
scaleType
:ImageView
缩放方式
其中ImageListener
是一个接口,包含两个方法:
public interface ImageListener extends ErrorListner {
void onResponse(ImageContainer response, boolean isImmediate);
}
public interface ErrorListner {
void onErrorResponse(VolleyError error);
}
其实就是处理两种情况:成功、失败,可以自己实现ImageListener
接口,也可以简单地调用ImageLoader.getImageListener()
方法获取实例。
接下来看下关键的get()
方法
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight, ScaleType scaleType) {
// 只能处理从主线程发出的请求
Threads.throwIfNotOnMainThread();
// 构建缓存key,当且仅当requestUrl、maxWidth、maxHeight、scaleType均相等时,key才会相同
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
// 优先查找缓存
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (chachedBitmap != null) {
// 如果找到缓存,直接响应,不再发送到网络请求队列
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
ImageContainer = imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
imageListener.onResponse(imageContainer, true);
// 判断是否已经有一个请求在执行,如果是,则不再重复发送请求
BatchedImageRequest request = mInFlightRequets.get(cacheKey);
if (request != null) {
request.addContainer(imageContainer);
return imageContainer;
}
// 新建一个图片请求并发送
Request<Bitmap> newRequest =
makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType, cacheKey);
mRequestQueue.add(newRequest);
// 将新的图片请求加入到飞行队列中
mInFlightRequests.put(cacheKey, new BachedImageRequest(newRequest, imageContainer));
return imageContainer;
}
看完这部分代码,我们来捋一下get()
方法的流程
- 首先,判断当前是否处于主线程,如果不是就抛出异常。即,
ImageLoader
的请求只允许在主线程发送,因为要处理图片返回的情况,不能在工作线程操作UI; - 根据url、最大宽高、缩放方式构建缓存key,用以查找缓存中是否以包含有该请求,若有直接响应更新UI;否则下一步;
- 判断是否已经有一个同样的请求在执行,若是,则不再发送该请求;
- 新建一个
ImageRequest
,并添加到RequestQueue
中进行分发
看到这里就可以发现我们在分析构造函数时的推测是正确的,ImageLoader
实际上就是对ImageRequest
的一个封装。
对Volley的解析到此结束。