Android Volley 研究综述(四)

文章目录
  1. 1. 深入 Volley
  2. 2. 常见网络请求框架的简单对比
  3. 3. 参考文献

人生是没有如果的游戏,尽力玩好现在就行了。

小半年前,在 Android Volley 研究综述(三)中,曾说过“未来某天完善”(四),即 Volley 源码的研究综述,本着有始有终的原则,在国庆长假的最后一天,来探究 Volley 的源码,虽说出了一些时新的网络请求框架如 OKHttp,Retrofit 等,但深入经典的 Volley 框架还是十分有必要的。

深入 Volley

Volley 的官方文档中有下面这一张工作流程图:

使用 Volley 时,第一步是调用 Volley.newRequestQueue(context) 方法,获取一个 RequestQueue 对象:

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

方法仅有一行代码,调用 newRequestQueue() 的方法重载,第二个参数传入 null,该方法源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {  
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
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) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}

Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}

第 12 行,若 stack 是等于 null,则去创建一个 HttpStack 对象,这里,若手机系统版本号大于 9,则创建一个 HurlStack 的实例,否则就创建一个 HttpClientStack 的实例。实际上,HurlStack 的内部,使用 HttpURLConnection 进行网络通讯,而 HttpClientStack 的内部,使用 HttpClient 进行网络通讯。HttpStack 创建好之后,接下来,创建一个 Network 对象,用于由传入的 HttpStack 对象来处理网络请求。而后,new 出一个 RequestQueue 对象,调用 start() 方法启动,最后,将 RequestQueue 返回。如此,newRequestQueue() 的方法执行结束。其中,RequestQueue 的 start() 方法内部执行代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}
}

先创建了一个 CacheDispatcher 的实例,然后,调用它的 start() 方法,接着,在一个 for 循环里,创建 NetworkDispatcher 的实例,分别调用它们的 start() 方法。此处,CacheDispatcher 和 NetworkDispatcher 都是继承自 Thread,而默认情况下,for 循环会执行 4 次,亦即当调用了 Volley.newRequestQueue(context) 后,会有 5 个线程一直在后台运行,不断等待网络请求的到来。其中,CacheDispatcher 是缓存线程,NetworkDispatcher 是网络请求线程。自然,得到 RequestQueue 之后,只需构建出相应的 Request,然后,调用 RequestQueue 的 add() 方法将 Request 传入就可以完成网络请求操作。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
37
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);
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.
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();
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.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}

第 11 行,判断当前的请求是否可以缓存,若不能缓存,则在第 12 行直接将该请求加入网络请求队列;若能缓存,则在第 33 行将该请求加入缓存队列。默认情况下,每条请求都是可以缓存的,可以人为地调用 Request 的 setShouldCache(false) 方法改变该默认行为。不作改动时,默认情况下,每条请求自然被添加到缓存队列中,于是,一直在后台等待的缓存线程开始运行,CacheDispatcher 中的 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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class CacheDispatcher extends Thread {  

……

@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;
}
}
}
}

挑重点看,第 11 行,看到一个 while(true) 循环,说明缓存线程始终在运行。接着,第 23 行尝试从缓存中取出响应结果,若为空,则将该请求加入到网络请求队列中;若不为空,就判断该缓存是否已经过期,若过期了则同样将该条请求加入到网络请求队列中,否则认为不需要重发网络请求,直接使用缓存中的数据。而后,第 39 行调用 Request 的 parseNetworkResponse() 方法对数据进行解析。最后,将解析出来的数据回调。此处,其逻辑与 NetworkDispatcher 后半部分的逻辑几乎相同。NetworkDispatcher 中,网络请求的代码如下所示:

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
public class NetworkDispatcher extends Thread {  
……
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request<?> request;
while (true) {
try {
// Take a request from the queue.
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
mDelivery.postError(request, new VolleyError(e));
}
}
}
}

同样,第 7 行有似 while(true) 的循环,说明网络请求线程在不断运行。第 28 行,调用 Network 的 performRequest() 方法去发送网络请求,Network 是一个接口,具体的实现是 BasicNetwork,performWorkRequest() 方法的代码如下:

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
public class BasicNetwork implements Network {  
……
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = new HashMap<String, String>();
try {
// Gather headers.
Map<String, String> headers = new HashMap<String, String>();
addCacheHeaders(headers, request.getCacheEntry());
httpResponse = mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// Handle cache validation.
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
request.getCacheEntry() == null ? null : request.getCacheEntry().data,
responseHeaders, true);
}
// Some responses such as 204s do not have content. We must check.
if (httpResponse.getEntity() != null) {
responseContents = entityToBytes(httpResponse.getEntity());
} else {
// Add 0 byte response as a way of honestly representing a
// no-content request.
responseContents = new byte[0];
}
// if the request is slow, log it.
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusLine);
if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
} catch (Exception e) {
……
}
}
}
}

第 14 行,调用了 HttpStack 的 performRequest() 方法,这里的 HttpStack 就是在一开始调用 newRequestQueue() 方法时创建的实例,之后会将服务器返回的数据组装成一个 NetworkResponse 对象进行返回。在 NetworkDispatcher 中,收到 NetworkResponse 的返回值,调用 Request 的 parseNetworkResponse() 方法解析 NetworkResponse 中的数据,以及将数据写入缓存,该方法的实现交给 Request 的子类完成,因为不同种类的 Request 解析的方式不同。解析完 NetworkResponse 中的数据后,调用 ExecutorDelivery 的 postResponse() 方法来回调解析出的数据,代码如下:

1
2
3
4
5
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {  
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}

其中,mResponsePoster 的 execute() 方法中,传入一个 ResponseDeliveryRunnable 对象,保证该对象的 run() 方法在主线程中运行,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
private class ResponseDeliveryRunnable implements Runnable {  
private final Request mRequest;
private final Response mResponse;
private final Runnable mRunnable;

public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
mRequest = request;
mResponse = response;
mRunnable = runnable;
}

@SuppressWarnings("unchecked")
@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();
}
}
}

第 22 行,调用 Request 的 deliverResponse() 方法,该方法即为自定义 Request 时需要重写的一个方法,每一条网络请求的响应都是回调到该方法中。最后,在该方法里,将响应的数据回调到 Response.Listener 的 onResponse() 方法中。这时,重看文章开头的图:

蓝色部分是主线程,绿色部分是缓存线程,橙色部分是网络线程。主线程中,调用 RequestQueue 的 add() 方法添加一条网络请求,该请求会被添加到缓存队列中。若发现可以找到相应的缓存结果,就直接读取缓存并解析,然后回调到主线程;若在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送 HTTP 请求,解析响应结果,写入缓存,并回调主线程。

常见网络请求框架的简单对比

Volley

Volley 将 AsyncHttpClient 和 Universal-Image-Loader 的优点集于一身,既可以简单地进行 HTTP 通信,又可以轻松地加载网络上的图片。不过,Volley 适合进行数据量不大但通信频繁的操作,对于大数据量的网络操作,如下载文件,会表现得非常糟糕。

OkHttp

OkHttp 是 Android 版 HTTP 客户端,非常高效,支持 SPDY、连接池、GZIP 和 HTTP 缓存。默认情况下,OkHttp 会自动处理常见的网络问题,像二次连接、SSL 的握手问题。从 Android 4.4 开始,HttpURLConnection 的底层实现采用的即是 OkHttp 。

Retrofit

Retrofit 支持同步和异步两种方式,在使用时,需要将请求地址转换为接口,通过注解来指定请求方法、请求参数、请求头和返回值等信息。性能最好、处理最快,默认使用 GSON 。

以上具体参见博文 Android 各大网络请求库的比较及实战

当下,网络请求的最佳实践即为 RxJava + Retrofit 了,虽然网络请求库多种多样,但其本质思想是一致的。就本系列对 Volley 的研究综述,虽然 Volley 距今三年了,也还有好多公司在用,虽旧但是经典,值得学习的地方还是很多。至此,Android Volley 研究综述到此结束。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流

参考文献

1.https://developer.android.com/training/volley/simple.html

2.http://blog.csdn.net/sinyu890807/article/details/17656437