Volley源码分析
Volley是一个可以让你在Android应用中以非常简单的方式来发送http请求并且处理服务器返回的数据,这样你就可以花更多的时间关心你的业务实现了
前段时间用了一下Volley,发现非常好用,就很好奇Volley是怎么实现的,因为它是Google的人开发的,也好奇大牛们写的代码都是怎么样的?抱着学习的心态来分析一下Volley的源码,也可以为我以后的工作中积累一些好用的解决方案。以后我也会看一下Android-async-http-client是如何来处理发送http请求这类问题的
Volley的使用可以看这篇博客:Volley库的使用介绍 ,你可以在这篇博客中找到更多关于Volley的学习资料:Android开发资源
首先说明一下,在Volley中每发送一个请求都需要创建一个Request
对象,然后将这个Request
放入一个RequestQueue
中,然后Volley会自动帮你处理这个Request
,你只需要处理返回的结果即可
从Volley的源码中可以看出,Volley就是实现了一个生产者消费者问题(Producer–consumer problem)。从源码里看,一个消费者(CacheDispatcher
)也可以是下一个消费者(NetworkDispatcher
)的生产者
##0x0 Volley架构
总的来说Volley库的架构非常清晰,源代码也非常好理解,Volley的架构可以用下面这张图来说明:
这张图是Google I/O 2013 大会上演讲的图,我是从Youtube的视频上截下来的。
从这张图上可以很清晰的看见,一个Request被放到RequestQueue中后,马上被CacheDispatcher
处理,从名字就可以看出来这是检查Cache的,如果Cache中有数据并且没过期的话Volley就直接将数据返回了
如果CacheDispatcher
中没有数据或,数据已经过期了,那么Volley就通过NetworkDispatcher
发送一个http请求来获取最新的数据,从图中可以看出有多个线程来发送http请求,也就是说Volley可以同时发送多个http请求
##0x1
上面将了Volley处理一个Request
的大致流程,那么从源码角度来看Volley是怎么处理的呢?
###0x10 Request
首先来看一下Volley的Request
类的签名:
public abstract class Request<T> implements Comparable<Request<T>>
Request
是一个范型,其中T
表示这个Request
返回的数据类型,并且这个Request
是可以相互比较的(实现Comparable
接口),Request
类实现的Comparable
接口就是比较两个Request
的priority
Volley自带的Request
包含了一下几个:
ImageRequest
JsonArrayRequest
JsonObjectRequest
JsonRequest
StringRequest
ClearCacheRequest
从字面上可以基本看出每个Request是用来干什么的。
你也可以自定义一个Request<T>
,比如说你可以定义一个GsonRequest<T>
,当Volley发送一个请求后返回的是一个Java对象(通过Gson将json字符串自动转换成一个Java对象),这样的话写代码就非常方便了
自定义一个Request<T>
需要实现Request
类的两个抽象方法:
Response<T> parseNetworkResponse(NetworkResponse response)
void deliverResponse(T response)
第一个方法用来说明如何解析这个Response,说白了就是将NetworkResponse
转换成Response
对象。比如ImageRequest
类就是需要将reponse数据转换成一个Bitmap对象
第二个方法是用来告诉Volley如何传递这个解析以后的结果,一般是用一个Response.Listener<T>
回调接口来传递这个结果
##0x2 RequestQueue
上面讲了如何使用Request
类,现在讲讲RequestQueue
创建一个RequestQueue
,需要4个参数
- Cache:Volley使用的缓存,默认是
DiskBasedCache
(这个Cache为了减少gc,自己实现了一个简单的序列化方法) - Network : 这个是
RequestQueue
用来发送http请求的工具,后面会详细讲 - 发送http请求的线程数(默认是4个):
NetworkDispatcher
线程数量 - ResponseDelivery :用于传递Response,一般是被
CacheDispatcher
和NetworkDispatcher
用于传递从Cache中拿到的或Network返回的HTTP Response
####RequestQueue包含的其他东西
上面讲的是Volley的RequestQueue
可以传入的参数,一个RequestQueue
还需要以下东西才能正常运行:
AtomicInteger mSequenceGenerator
是一个序列号生成器,它为每一个加入到队列中的Request打上一个序列号
PriorityBlockingQueue<Request<?>> mCacheQueue
表示在Cache队列中的Request,从最上面的图中可以看到,一个Request
被加到队列中后,它首先就进入mCacheQueue
PriorityBlockingQueue<Request<?>> mNetworkQueue
表示等待通过Network
发送HTTP请求的Request
队列。这个和上面的mCacheQueue
都是一个优先队列,每一个加入到优先队列中的Request
都会按照它的优先级进行排序(如果优先级一样的话,就通过mSequenceGenerator
生成的序号进行排序,这部分代码在Request.compareTo
中可以看到)
Set<Request<?>> mCurrentRequests
是RequestQueue
当前正在执行或正在等待的Request
,在mCacheQueue
或mNetworkQueue
中的Request
都会被存放到mCurrentRequests
列表中。这个列表中的Request
可以用来执行RequestQueue.cancelAll
操作
Map<String, Queue<Request<?>>> mWaitingRequests
这个对象也很有意思,这个map的key是Request
的cacheKay,value是一个对应这个cacheKey所有的Request
###0x20 创建一个RequestQueue
一般创建一个RequestQueue
的方法就是通过下面这句代码:
RequestQueue queue = Volley.newRequestQueue(Context);
当然,如果你需要自定义的话,就可以通过RequestQueue
的构造函数自己new一个RequestQueue
。一般来说,一个app中有一个RequestQueue
就够了,没必要生成多个队列,生成多个队列会浪费系统的线程资源
在创建一个RequestQueue
的时候,Volley已经充分帮我们考虑了一些事情
Android中用来发送HTTP请求的类有HttpUrlConnection
和AndroidHttpClient
,一般会使用前者,因为Google对它做了很多优化,但是在Android Gingerbread(Android 2.3,API LEVEL = 9)以前,HttpUrlConnection
会有一些bug,那么应该使用后者。那为什么不直接使用后者呢?因为Google对前者做了很多优化…=。=
Volley对此也做了相应的判断,Android 2.3以前使用AndroidHttpClient
,Android 3.0之后使用HttpUrlConnection
,这样就会有相对更好的性能,更节省流量(看这里)
Network
接口对他们进行了封装,对RequestQueue
而言只有Network
接口,但是真正发送请求的地方在HurlStack.performRequest
或HttpClientStack.performRequest
方法中,前者使用HttpUrlConnection
,后者使用AndroidHttpClient
####好处RequestQueue
只依赖于Network
接口,而不依赖于实现,它不必关心Network.performRequest
具体的实现方式
###0x21 启动和停止RequestQueue
在RequestQueue
生成以后就需要调用RequestQueue.start
方法启动
RequestQueue.start
方法就是启动每一个Dispatcher(每一个Dispatcher就是一个线程)
/**
* Starts the dispatchers in this queue.
*/
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.stop
方法就是停止每一个Dispatcher,代码就不贴了
###0x22 向RequestQueue中添加Request
每创建一个Request
都需要通过RequestQueue.add
方法添加到队列中。调用RequestQueue
的add
方法,这就是生产者消费者问题中,生产者(producer)向池子(queue)里面存放物品(goods),然后消费者从池子(queue)里拿东西
先看代码:
/**
* Adds a Request to the dispatch queue.
* @param request The request to service
* @return The passed-in request
*/
public <T> Request<T> add(Request<T> request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
// A
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
// B
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
// C
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// Insert 'null' queue for this cacheKey, indicating there is now a request in flight.
// D
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
上面代码就是添加一个Request
到队列中的全部操作。我加了A、B、C、D关键处标记,
A : 首先,将
Request
加入到mCurrentRequests
,表示有一个Request
将要被处理,然后这是这个Request
的序列号和一个测试标记B : 检查这个
Request
是否可以被Cache,如果不可以,就直接放入mNetworkQueue
中,由NetworkDispatcher
处理,直接发送HTTP请求C : 接下来,表示
Request
都可以被Cache的,检查Request
的cacheKey字段,如果存在相同cacheKey的Request
,就表示可以不处理这个Request
了,直接拿别的Request
的结果即可,这样可以减少相同Request
的发送,减少流量消耗~D : 如果C不满足的话,就将这个
Request
放入mCacheQueue
队列中,由CacheDispatcher
来处理
###0x23 如何传递Response
当CacheDispatcher
或NetworkDispatcher
拿到Response以后(不管是Cache中的还是请求HTTP返回的),都会通过一个叫做ResponseDelivery
的东西传递出去。
其中,传递HTTP Response真正的地方就在ResponseDelivery
的ResponseDeliveryRunnable
类中,下面是ResponseDeliveryRunnable
类的run
方法:
@Override
public void run() {
// If this request has canceled, finish it and don't deliver.
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
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) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
// If we have been provided a post-delivery runnable, run it.
if (mRunnable != null) {
mRunnable.run();
}
}
这个方法就会检查这个Response
,如果Response
是成功的,那么就通过Request.deliverResponse
方法传递。
在本文最上面可以看到,Request.deliverResponse
是一个抽象方法,每个Request
都需要自己实现处理Response
的代码,一般都是通过调用Response.Listener
接口来处理结果
##0x3 CacheDispatcher
CacheDispatcher
是一个专门用来检查Cache的线程,它其实既是消费者也是生产者。它需要以下几样东西:
BlockingQueue<Request<?>> mCacheQueue
: 等待检查Cache 的Request队列BlockingQueue<Request<?>> mNetworkQueue
: 等待发送HTTP请求的Request队列Cache mCache
: Volley使用的Cache,默认就是DiskBasedCache
ResponseDelivery mDelivery
:如果检查的Cache符合要求(没有过期),就用这个来传递Response
从上面图中可以看出,CacheDispatcher
对于app来说是一个消费者,它从mCacheQueue
中拿Request来检查Cache。对NetworkDispatcher
来说,又是一个生产者(如果检查的Cache没有或者Cache已经过期,就向mNetworkQueue
中添加Request)
下面就是CacheDispatcher
的run
方法,从源代码里看一看到CacheDispatcher
会一直运行,当mCacheQueue
中没有需要处理的Request
时,此线程会一直被mCacheQueue.take()
方法阻塞,直到RequestQueue
向mCacheQueue
添加了一个请求。
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
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.
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
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()) {
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.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
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.
if (mQuit) {
return;
}
continue;
}
}
}
##0x4 NetworkDispatcher
NetworkDispatcher
和CacheDispatcher
非常类似,它也需要4个东西:
BlockingQueue<Request<?>> mNetworkQueue
: 等待发送HTTP请求的Request队列Network mNetwork
: 用于发送HTTP请求的,Network
是一个接口Cache mCache
: Volley使用的Cache,默认就是DiskBasedCache
ResponseDelivery mDelivery
:如果检查的Cache符合要求(没有过期),就用这个来传递Response
其中操作的过程也和CacheDispatcher
类似,就不贴源码了
##0x5 ImageLoader
上面讲的东西大致就构成了Volley库最核心的东西,一般的请求json或图片是没有太大问题的。但是Google的工程师又想到为我们广大码农减少工作量,增加了ImageLoader
和NetworkImageView
这两个东西
其实ImageLoader
这个工具和Volley库的整个架构是没有太大关系的,它就是为了更加方便的让我们加载网络图片
创建一个ImageLoader
需要两个东西:
- RequestQueue : 这个在上面已经说了好多了
- ImageCache : 这是一个接口,Volley没有提供具体的实现,需要自己实现。一般都会实现一个基于内存的
LruImageCache
,网上也有各种栗子。可以参考Picasso源码中的实现
ImageLoader
也是对RequestQueue
的一个封装,它也会为创建一个Request<ImageView>
,然后把这个Request放到RequestQueue中去下载图片
###0x50 在Volley中如何方便的加载一张图片
ImageLoader loader = new ImageLoader(RequestQueue, ImageCache);
NetworkImageView imageView = (NetworkImageView)findViewById(R.id.iv_news_pic);
imageView.setImageUrl(picUrl, loader);
上面3句代码就能够加载网络图片了,是不是很方便。NetworkImageView
会计算当前ImageView的宽高,然后创建一个ImageRequest,放入RequestQueue中下载图片。
###0x51 NetworkImageView
NetworkImageView
是继承ImageView
的,比传统的ImageView
多了一个加载网络图片的功能
##0x6 最后
本文就是详细的分析了Volley库的结构了流程。本文觉得这个库设计的非常好,不管是扩展性还是代码可读性都非常好。
RequestQueue
就表示Volley运行的整个生命周期,它维护了很多个队列。CacheDispatcher
和NetworkDispatcher
分别完成检查Cache和发送HTTP请求的工作。当拿到NetworkResponse
数据的时候,会调用Request.parseNetworkResponse
方法解析Response结果,最后调用ResponseDeleivery
对象来传递解析后的Response结果。
Google还在Volley中加入了ImageLoader
和NetworkImageView
让我们广大码农以更加方便的方式加载网络图片
###最后的最后
Volley的整个架构实现就是最基本的生产者消费者模式(Producer–consumer problem)。这种架构虽然很简单,但是用途也非常广泛,非常值得学习和借鉴!!
也不是说Volley就完美了,最近本人又在学习Picasso的源码。这是一个非常好的专门用于加载图片的库,它不光可以加载网络图片,还可以用它来加载res目录下的图片、SD卡上的图片、Asset目录下的图片、手机图片库中的图片,而且扩展性也非常好!不说了,下次有机会就写一篇关于Picasso的源码分析博客