Volley的原理解析

前言

在 Android 开发编程的过程当中,无可避免地需要涉及到网络编程,这就需要我们开发者比较熟练地掌握网络编程。哈哈,今天就先从解析 Volley 开始吧!

1.0 Volley是什么

Volley 是在 2013 年的 Googel I/O 大会上面推出的一个 HTTP 库,它可以帮助 Android 应用更加方便地执行网络请求。它既可以访问网络取得数据,同时还可以访问网络取得图片。

2.0 Volley的优缺点

Volley 有以下的优点:
  • 自动调度网络请求
  • 高并发网络连接
  • 通过标准的 HTTP cache coherence(高速缓存一致性)缓存磁盘的内存透明的响应
  • 支持指定请求的优先级
  • 撤销请求 API,或者指定取消请求队列中的一个区域
  • 框架容易被定制。例如,定制重试或者回调功能
  • 强大的指令(Strong ordering)可以使得异步加载网络数据并正确地显示到 UI 的操作更加简单
  • 包含了调试与追踪工具
Volley 的缺点:
  • 不适合用来下载大的数据文件

3.0 Volley的网络请求队列

使用 Volley 的流程是,创建一个 RequestQueue(请求队列)对象,然后把 Request(请求)提交给它。

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
// 代码[1]

final TextView textView = (TextView) MainActivity.this.findViewById(R.id.tv);

//[1.0]创建 RequestQueue 的实例
RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this); // 1

String url = "http://gank.io/api/data/Android/10/1";

//[2.0]构造一个 request(请求)
StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
textView.setText("Response is: " + response.toString());
}
}, new Response.ErrorListener() {

@Override
public void onErrorResponse(VolleyError error) {
textView.setText("Error is happenning");
}
});

//[3.0]把 request 添加进请求队列 RequestQueue 里面
requestQueue.add(request);

上面代码逻辑主要是通过构造一个 StringRequest 的实例,然后把这个实例添加进请求队列 RequestQueue 里面。我们来查看注释 1 的 Volley.newRequestQueue(Context) 方法的源码:

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
// 源码[2]

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); // 1

String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}

if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) { // 2
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}

Network network = new BasicNetwork(stack);

RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); // 3
queue.start(); // 4

return queue;
}

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

从上面的源码可以看出,newRequestQueue(Context)newRequestQueue(Context, HttpStack) 的重载方法,下面我们主要来看 newRequestQueue(Context, HttpStack) 方法,在注释 1 处通过 new File(File, String) 初始化构建一个 cacheDir 的缓存(这是一个名词),在注释 3 处,new DiskBasedCache(cacheDir) 为这个缓存分配了 5M 的存储空间;
回到注释 2, 当 SDK 的版本大于或等于 9 的时候,也就是 Android 的版本号大于或等于 2.3,则创建基于 HttpURLConnectionHurlStack,否则就创建基于执行请求的 HttpClientHttpClientStack,然后在注释 3 处,通过 new RequestQueue(Cache, Network) 创建请求队列,我们来查看下 new RequestQueue(Cache, Network) 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
// 源码[3]

...

private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

...

public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
...

构造方法 new RequestQueue(Cache, Network) 为网络请求分配了 4 条线程进行请求。对于 RequestQueue 这个类,主要的作用是作为一个线程池用于调度队列中的请求:当调用 add(Request) 将会把传进的请求(Request)在缓存队列(cache)或者网络队列(network)中解析,然后传递回主线程,也就是 代码[1] 里面的回调函数 onResponse(String)onErrorResponse(VolleyError) 拿到回调的请求内容;
在 代码[1] 的注释 4 处,调用了 add(Request) 方法:

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
37
38
39
40
41
42
43
44
// 源码[4]

public <T> Request<T> add(Request<T> request) {
request.setRequestQueue(this);
//同步代码块,保证 mCurrentRequests.add(request) 在一个进程里面的所有线程里面,有且只能在
//一个线程里面执行
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}

request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");

//如果不可以存储,就把请求(request)添加进网络调度队列里面
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}

//如果可以存储:
//同步代码块
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
//如果之前有相同的请求并且还没有返回结果的,就把此请求加入到 mWaitingRequests 里面
if (mWaitingRequests.containsKey(cacheKey)) {
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
//如果没有请求在进行中,重新初始化 Queue<Request<?>> 的实例
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 {
//如果没有的话,就把请求添加到 mCacheQueue 队列里面
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
}

从上面的 add(Request) 方法的源码可以看出,主要的逻辑是:如果请求(request)不可以缓存,就直接添加进缓存队列,否则添加进缓存队列; 当拿到 mNetworkQueuemCacheQueue 以后,就把请求返回并且调用 start() 方法(如 源码[2] 的注释 4),当查看 start() 方法的源码时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 源码[5]

public void start() {
stop(); // 确保当前的缓存调度和网络调度都已经停止
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();
}
}

里面主要是创建 CacheDispatcher(BlockingQueue<Request<?>>, BlockingQueue<Request<?>>, Cache, ResponseDelivery) 的实例并通过 mCacheDispatcher.start() 开启缓存调度线程,和创建 NetworkDispatcher(BlockingQueue<Request<?>>,Network, Cache,ResponseDelivery) 的实例并通过 networkDispatcher.start() 开启网络调度线程。

4.0 网络调度线程NetworkDispatcher

网络调度线程 NetworkDispatcher 是一个继承于 Thread 的线程,通过查看其任务方法 run() 的源码:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 源码[6]

@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request<?> request;
while (true) {
try {
// 从网络队列中取出请求
request = mQueue.take();
} catch (InterruptedException e) {
if (mQuit) {
return;
}
continue;
}

try {
request.addMarker("network-queue-take");

// 如果请求被取消
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}

addTrafficStatsTag(request);

NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");

if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}

Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");

// 把网络请求到的实体缓存到 源码[2] 的注释 1 处的本地缓存文件里面
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}

request.markDelivered();
// 回调请求到的响应给主线程
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
...

} catch (Exception e) {
...

}
}
}

网络调度的主要逻辑是,先判断网络请求有没有被取消,如果没有被取消,就通过 mNetwork.performRequest(request) 执行请求网络响应,拿到响应以后,先缓存在本地,然后再回调给主线程。

5.0 缓存调度线程CacheDispatcher

缓存调度线程 CacheDispatcher 的源码如下所示:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

mCache.initialize();

while (true) {
try {
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");

//如果请求被取消
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}

// 尝试在本地缓存中取回数据
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
//本地缓存丢失或者没有
request.addMarker("cache-miss");
mNetworkQueue.put(request);
continue;
}

// 本地缓存过期
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}

// 命中缓存
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");

if (!entry.refreshNeeded()) {
// 回调相应给主线程
mDelivery.postResponse(request, response);
} else {

request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);

response.intermediate = true;

mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
...
}
}
});
}

} catch (InterruptedException e) {
...
}
}
}

同样地,如果请求没有被取消,就开始在取回本地的缓存,当本地的缓存不存在、丢失或者已经过期,就把请求添加到网络请求队列,当命中本地缓存,就把缓存的响应回调给主线程;

6.0 Volley原理解析

为了发送一个请求,你只需要构造一个请求并通过 add() 方法添加到 RequestQueue 中。一旦添加了这个请求,它会通过队列,通过一系列的调度,然后得到原始的响应数据并返回。

当执行 add()方法时,Volley触发执行一个缓存处理线程以及一系列网络处理线程。当添加一个请求到队列中,它将被缓存线程所捕获并触发: 如果这个请求可以被缓存处理,那么会在缓存线程中执行响应数据的解析并返回到主线程。如果请求不能被缓存所处理,它会被放到网络队列中。网络线程池中的第一个可用的网络线程会从队列中获取到这个请求并执行HTTP操作,解析工作线程的响应数据,把数据写到缓存中并把解析之后的数据返回到主线程。

一个请求的生命周期

The lifecycle of a request

小结

到此,Volley 库的分析就到先到此暂时结束了。这次,笔者主要是就当一个请求添加进请求队列之后,到其返回响应的过程进行了分析,了解到了 Volley 是如何把一个请求进行调度,然后得到响应并返回的。哈哈,感谢你的阅读…


源码下载 TestVolley

本文标题:Volley的原理解析

文章作者:

发布时间:2017年10月22日 - 00:10

最后更新:2021年06月20日 - 19:06

原始链接:https://hndroid.github.io/2017/10/22/Volley%E7%9A%84%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。