Volley源码解析

本文将按照Volley的使用流程来分析Volley的源码,在此期间依次了解Volley中的各个类,从而搞懂Volley的实现原理。

关于Volley的使用,我们都是从下面一行代码开始的,我们就从这个方法开始分析。

1
Volley.newRequestQueue(this);  

Volley类的实现:

1
2
3
4
5
6
7
8
9
10
11
public class Volley {  

...
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
...
}

public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
}

Volley类只有两个创建RequestQueue的方法,而主要的创建RequestQueue的方法就是包含两个参数Context和HttpStack的newRequestQueue方法,另外一个只是调用了这个方法。这个方法的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {  
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);//缓存文件

String userAgent = "volley/0";//UserAgent用来封装应用的包名跟版本号,提供给服务器,就跟浏览器信息一样
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}

if (stack == null) {//一般我们都不需要传这个参数进来,而volley则在这里会根据SDK的版本号来判断
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();//SDK如果大于等于9,也就是Android 2.3以后,因为引进了HttpUrlConnection,所以会用一个HurlStack
} else {//如果小于9,则是用HttpClient来实现
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}

Network network = new BasicNetwork(stack);//创建一个Network,构造函数需要一个stack参数,Network里面会调用stack去跟网络通信
//创建RequestQueue,并将缓存实现DiskBasedCache和网络实现BasicNetwork传进去,然后调用start方法
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();

return queue;
}

简要说明一下步骤:

  1. 创建缓存文件和UserAgent字符串
  2. 根据SDK版本来创建HttpStack的实现,如果是2.3以上的,则使用基于HttpUrlConnection实现的HurlStack,反之,则利用HttpClient实现的HttpClientStack。
  3. 创建一个BasicNetwork对象,并将HttpStack封装在Network中
  4. 创建一个DiskBasedCache对象,和Network一起,传给RequestQueue作为参数,创建RequestQueue对象。
  5. 调用 RequestQueue的 start 方法,然后返回创建的queue对象。

我们先分析HttpStack和Network,最终的网络请求是通过这两个接口的具体实现类来实现的。建议阅读:Volley-HttpStack及其实现类源码解析&&Volley-Network及其实现类的源码解析

然后分析DiskBasedCache,DiskBasedCache是Volley中实现硬盘缓存的工具具,建议阅读:Volley的硬盘缓存–Cache,DiskBasedCache,NoCache的源码分析

接下来我们分析RequestQueue的源码,首先看RequestQueue的构造函数:

1
2
3
public RequestQueue(Cache cache, Network network) {  
this(cache, network,DEFAULT_NETWORK_THREAD_POOL_SIZE);//跟网络交互的线程数量,默认是4
}
1
2
3
public RequestQueue(Cache cache, Network network, int threadPoolSize) {  
this(cache, network, threadPoolSize,new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
1
2
3
4
5
6
7
public RequestQueue(Cache cache, Network network, int threadPoolSize,  
ResponseDelivery delivery) {
mCache = cache;//缓存
mNetwork = network;//网络
mDispatchers = new NetworkDispatcher[threadPoolSize];//线程池
mDelivery = delivery;//派送Response的实现
}

在构造函数中,传入了在Volley类中创建的DiskBasedCache和BasicNetwork。

另外,通过前面传进来的线程数量(默认是4),会创建一个NetworkDispatcher的数组,也就是创建了一个有4个线程的线程池,因为NetworkDispatcher是继承于Thread的类,NetworkDispatcher的主要作用就是执行网络请求,从网络获取数据,关于NetworkDispatcher,建议阅读:Volley-NetworkDispatcher源码解析

此外,在构造方法中传入了一个ExecutorDelivery,ExecutorDelivery是对响应结果进行分发的一个类,关于ExecutorDelivery,建议阅读:Volley-ResponseDelivery及其实现类的源码解析。构造ExecutorDelivery的时候,其参数是一个Handler,而Handler的构造函数参数则是Looper.getMainLooper(),所以Handler其实是主线程中的Hanlder。

1
new ExecutorDelivery(new Handler(Looper.getMainLooper()))

主要作用也就是利用Handler来将Response传回主线程进行UI更新,因为我们知道,UI的更新必须在主线程。

接下来看RequestQueue的start方法,不过在介绍start方法前,先介绍两个队列。

1
2
3
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();

private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();
  • mCacheQueue 优先级阻塞队列,用来存放可以使用缓存的请求
  • mNetworkQueue 优先级阻塞队列,用来存放不可以使用混存的请求

start方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void start() {
stop(); // 保证所有正在运行的Dispatcher(也就是线程)都停止
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);// 创建缓存的派发器(也是一个线程),并启动线程。
mCacheDispatcher.start();
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
//停止缓存线程跟所有的网络线程
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}

我们可以看到,
1)start方法的一开始,会先调用stop方法。stop会将缓存线程还有所有的网络线程停止。

2)重新创建一个缓存线程CacheDispatcher,并启动,在这里,会将mCacheQueue,mNetwrok, mCache 和 mDelivery 传给其构造函数,关于缓存线程,建议阅读:Volley-CacheDispatcher源码解析

3)根据线程池的大小,创建相对应数目的网络线程并启动,而在这里,我们可以看到会将mNetworkQueue,mNetwrok,mCache 和 mDelivery作为参数传给NetworkDispatcher。

很明显,当调用RequestQueue的start方法的时候,其实也就是启动了一个缓存线程和默认的4个网络线程,它们就会在后面静静地等待请求的到来。

接下来看add方法,同样在介绍add方法前,也得介绍两个集合:

1
2
3
private final Map<String, Queue<Request<?>>> mWaitingRequests =  new HashMap<String, Queue<Request<?>>>();  

private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
  • mWaitingRequests 等待中的请求集合
  • mCurrentRequests 所有在队列中,或者正在被处理的请求都会在这个集合中

add方法的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public <T> Request<T> add(Request<T> request) {
//将请求的队列设置为当前队列,并将请求添加到mCurrentRequests中,表明是正在处理中的,而在这里,我们可以看到利用synchronized来同步
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
//在这里会设置序列号,保证每个请求都是按顺序被处理的。
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
//如果这个请求是设置不缓存的,那么就会将其添加到mNetworkQueue中,直接去网络中获取数据
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// 到这里,表明这个请求可以去先去缓存中获取数据。
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
//如果这个请求已经有一个相同的请求(相同的CacheKey)在mWatingRequest中,那么就要将相同CacheKey的请求用一个LinkedList给装起来,先不需要处理,等那个正在处理的请求结束后,再看看应该怎么处理。
if (mWaitingRequests.containsKey(cacheKey)) {
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 {
//如果mWaitingRequest中没有,那么就将其添加到集合中,将添加到mCacheQueue队列中,表明现在这个cacheKey的请求已经在处理了。
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}

代码分析都在注释中,这里就不再单独拿出来分析了。到这里,我们的请求就会在缓存线程或者网络线程中去处理了,当它们结束之后,每一个Request就会调用自身的finish方法,如下:

1
2
3
4
void finish(final String tag) {  
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}

而在这里,它调用的其实是 RequestQueue的finish方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void finish(Request<?> request) {  
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null) {
if (VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
mCacheQueue.addAll(waitingRequests);
}
}
}
}

可以看到,第一步就是将请求从mCurrentRequests中移除,正好对应了上面add方法中的添加。第二步就是判断这个请求有没有缓存,如果有,那么我们这个时候,将前面mWaitingQueue中相同CacheKey的一大批请求再一股脑儿的扔到mCacheQueue中,为什么现在才扔呢?因为前面我们不知道相同CacheKey的那个请求到底在缓存中有没有,如果没有,它需要去网络中获取,那就等到它从网络中获取之后,放到缓存中后,它结束了,并且已经缓存了,这个时候,我们就可以保证后面那堆相同CacheKey的请求可以在缓存中去取到数据了,而不需要再去网络中获取了。

在RequestQueue中,还提供了两个方法去取消请求,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void cancelAll(RequestFilter filter) {  
synchronized (mCurrentRequests) {
for (Request<?> request : mCurrentRequests) {
if (filter.apply(request)) {
request.cancel();
}
}
}
}

public void cancelAll(final Object tag) {
if (tag == null) {
throw new IllegalArgumentException("Cannot cancelAll with a null tag");
}
cancelAll(new RequestFilter() {
@Override
public boolean apply(Request<?> request) {
return request.getTag() == tag;
}
});
}

如上,第一个cancleAll会获取一个RequestFilter,这是RequestQueue的内部接口,定义如下:

1
2
3
public interface RequestFilter {  
public boolean apply(Request<?> request);
}

我们需要自己去实现,什么样的请求才是符合我们的过滤器的,然后在cancel中根据我们定义的过滤规则去批量地取消请求。而第二个则是利用创建Request时设置的Tag值,实现RequestFilter,然后调用上一个cancelAll方法,来取消一批同个Tag值的请求。
这两个方法(其实是一种,主要是利用Tag来批量取消请求)跟我们这个流程的关系不大,所以就不在这里多讲了。

至此Volley的源码就已经介绍完了,在这里我们总结下Volley的实现原理:

Volley创建一个RequestQueue请求队列,在RequestQueue中存在两个优先级队列,一个是缓存请求队列,另一个是网络请求队列,缓存请求队列中存放的是允许使用本地缓存的请求,网络请求队列中存放的是不允许使用缓存或者是本地不存在缓存的请求。当调用RequestQueue的add()方法添加Request的时候,会根据请求的一个参数shouldCache,来判断要不要去缓存中查询,如果是去缓存中查询,那么就会把请求放到CacheQueue中。,如果不允许缓存,就直接将请求放入网络请求队列。

调用RequestQueue请求队列的start方法后,会开启一条缓存线程四条请求线程。缓存线程从缓存请求队列中,取出请求,查找本地缓存,如果查找不到本地缓存,就将这个请求放入网络请求队列中去,如果查找到混存,就直接分发缓存结果。请求线程,负责从网絡请求队列中取出请求,从网络中获取数据,获取到数据后进行分发。

感谢阅读,如有不正确的地方,欢迎指出。

部分资料链接:

使用篇:

Android Studio 中引入Volley

第2步中改为:File–>New–>import Module 引进volley就好了,剩下的步骤同文章。

Android Volley初探:Volley基本用法

Android Volley完全解析(一),初识Volley的基本用法
Android Volley完全解析(二),使用Volley加载网络图片(含ImageLoader)
Android Volley完全解析(三),定制自己的Request

Android常用开源框架之Volley基础
使用Volley加载、缓存图片(ImageLoader)(含ImageLoader)

更深入的使用:

Volley框架的使用

整体源码剖析:

volley学习笔记(有源于源码阅读的建议)
Volley 源码解析(流程图比较多)

Volley的使用以及源码分析(二)

Volley库源码解析(有ImageLoader,讲的不错,讲到图片加载ImageRequest的的节约内存的细节和Volley为什么不适合大数据量的网络通信)

Volley源码解析(讲的很详细)

(干货)Android Volley框架源码详细解析

Android Volley完全解析(四),带你从源码的角度理解Volley(比较简略,主抓流程分析)

Volley源码解析

Volley源码解读(含—-ImageRequest—-volley gson改造—-volley okhttp改造)

正常的思维解读 Volley 的使用以及源码(二)

部分源码剖析:

Android中关于Volley的使用(五)从RequestQueue开始来深入认识Volley(已经参考完毕)

使用问题:

Volley框架请求取消的实现

为什么说Volley适合数据量小,通信频繁的网络操作

Volley在下载时会将响应数据读取到内存中的一个byte[]作为缓冲,如果用来下载大文件, 内存可能溢出,Volley的线程池默认只有4个线程,在多余4个时其他请求就会被迫等待, 所以即便用Volley下载不会溢出的文件, 也是效率很低

保留问题:

大小端问题

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器