Volley解析

Volley是Gooogle I/O 2013发布的一个处理和缓存网络请求的库,能使网络通信更快,更简单,更健壮。Volley名称的由来: a burst or emission of many things or a large amount at once。

2013年volley就有了,分析Volley的文章网上也是一堆一堆的,不过Volley的设计还是值得研究的。

volley

Volley feature

  • Volley automatically schedule all network requests. It means that Volley will be taking care of all the network requests your app executes for fetching response or image from web.
  • Volley provides transparent disk and memory caching.
  • Volley provides powerful cancellation request API. It means that you can cancel a single request or you can set blocks or scopes of requests to cancel.
  • Volley provides powerful customization abilities.
  • Volley provides Debugging and tracing tools

请求调度

在Android Developer上有Volley的请求调度流程图

从入口分析源码

我们从使用开始分析源码(一般来说,分析别人的源码都是这样的)

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
public class MyApplication extends Application{
public static RequestQueue queues;

@Override
public void onCreate() {
super.onCreate();
queues = Volley.newRequestQueue(getApplicationContext());
}

public static RequestQueue getHttpQueues() {
return queues;
}
}

private void volley_get(){
String url = "http://ip.taobao.com/service/getIpInfo.php?ip=202.96.128.1";
StringRequest request = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>(){
@Override
public void onResponse(String response) {}
},
new Response.ErrorListener(){
@Override
public void onErrorResponse(VolleyError error)
{}

}
);
requestTag = "volley_get";
request.setTag(requestTag);
MyApplication.getHttpQueues().add(request);
}

使用起来非常简单,Volley.newRequestQueue(getApplicationContext());新建一个RequestQueue单例(为什么是一个?因为方便维护RequestQueue,下面看源码说),然后使用RequestQueue.add(Request request)就可以等待返回结果了。

Volley–>工具类

首先,新创建了一个RequestQueue,我们看下RequestQueue创建的源码。
Volley.java

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
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
//1、默认设置缓存的路径为/data/data/packagename/cache/volley
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
//2、设置userAgent为包名+版本号
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
//3、根据android sdk版本,使用不同的stack,9以上使用HttpURLConnection,9以下使用HttpClient
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
//4、根据HttpStack设置Network,这个是真正访问网络请求数据的。
Network network = new BasicNetwork(stack);
//5、maxDiskCacheBytes默认为-1,这个是用来设置最大的缓存byte
RequestQueue queue;
if (maxDiskCacheBytes <= -1)
{
// No maximum size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
}
else
{
// Disk cache size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
}
//最后启动线程,并且返回queue
queue.start();
return queue;
}

RequestQueue.java

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

这里默认启动了1个CacheDispatcher线程和4个NetworkDispatcher线程不断的获取Request。因为是BlockingQueue所以如果Queue里没有Request则阻塞住。

Request –>请求封装类

Volley中每一次请求被封装为一个Request对象,Request是个抽象类,通过设置Request的属性,来自定义请求时的方式。如:

  • url– 请求的url
  • method– 请求的方式:GET/POST/PUT/DELETE等等
  • mRequestQueue– 该request和它被放入的mRequestQueue进行了绑定,用于在Request请求结束后,告诉mRequestQueue把该request从请求等待队列中移除。
  • mRetryPolicy– 失败的重试策略
  • mErrorListener– 请求失败的监听器
  • mIdentifier– 该request的唯一标示

当然还有一个重要的parseNetworkResponse抽象方法。Request有多个子类,如StringRequest,ImageRequest,JsonRequest等。在parseNetworkResponse方法中进行各自的解析。

1
2
3
4
5
6
7
8
9
/**
* Subclasses must implement this to parse the raw network response
* and return an appropriate response type. This method will be
* called from a worker thread. The response will not be delivered
* if you return null.
* @param response Response from the network
* @return The parsed response, or null in the case of an error
*/
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

RequestQueue –>核心调度类

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
/**
* 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.
//1、先设置request的RequestQueue,方便request在结束时回调
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
//2、将request存入set
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.
//3、如果该request不需要缓存,则直接扔给mNetworkQueue
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;
}
}

先总结下:
1、每个request都设置requestQueue,方便request请求完成时的回调
2、如果request不需要缓存,则直接放入mNetworkQueue,NetworkDispatcher进行网络请求
3、如果request已经在请求队列里了,同一个请求的判断标准是method+url,则不进行请求,暂时放到mWaitingRequests里。等待第一个请求完成后,统一的进行返回。
否则,放入mCacheQueue和mWaitingRequests。由CacheDispatcher线程进行处理。

接下来扫一眼CacheDispatcher

CacheDispatcher –> 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
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
76
77
78
79
80
81
82
@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;
}
//没命中cache,放入mNetworkQueue
// 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;
}
//cache过期了,放入mNetworkQueue重新请求
// 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;
}
//命中cache,进行解析
// 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");
//TTL (生存时间值)是否小于当前时间
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
//超过存活时间,先将Response发出,然后再启动一个新线程请求网络。
// 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;
}
}
}

NetworkDispatcher –> 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
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
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request<?> request;
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
// release previous request object to avoid leaking request object when mQueue is drained.
request = null;
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()) {
//finish,调用RequestQueue.finish,把request从等待队列里进行删除,并且通知请求的监听器
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();
//通过handler来响应request和response。
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
}

还有网络请求那块没有说明,这个比较简单了。大家还是看下源码理解比较清晰。
还有遗留的那个为什么要使用一个RequestQueue?原因也很明确了,这样方便Request的管理。如果一个请求就新建一个RequestQueue,不是不可以,而是太浪费了。因为每个RequestQueue都会默认开启5个线程。

##Volley的缺陷
参考自codekk
1、缓存的再验证方面,在构建If-Modified-Since请求首部时,Volley 使用了服务端响应的Date首部,没有使用Last-Modified首部。整个框架没有使用Last-Modified首部。这与 Http 语义不符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
// If there's no cache entry, we're done.
if (entry == null) {
return;
}

if (entry.etag != null) {
headers.put("If-None-Match", entry.etag);
}

if (entry.serverDate > 0) {
Date refTime = new Date(entry.serverDate);
headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
}
}

2、RequestQueue使用PriorityQueue保存request队列,但是PriorityQueue默认最多只能保存11个请求,超出则报错。所以生成request的速度不能大于消费request的速度,这应该是个坑。使用的时候可以设置稍微大点的capacity。
3、初始化RequestQueue时候,使用Volley.newRequestQueue(Context context);操作失误的话容易造成内存泄露。这里应该使用context.getApplicationContext()。应该在源码里进行控制。
4、Volley加载大图容易OOM。不建议使用Volley来显示图片。

总结

最后,理一下Volley的值得学习的点:
1、设置统一的框架访问入口(Volley)
2、使用组合和继承
1)封装了Request、Response
2)对Request进行抽象,不同的请求实现各自的解析方法即可
3)对于网络请求抽象HttpStack,不同的sdk使用不同的请求方法
3)Request的添加和处理分离。Request只需要放入RequestQueue,处理的时候,由各自的线程来处理,很好的利用了BlockingQueue的优势:阻塞和线程安全。

参考自:
Volley from codekk
Volley from 工匠若水