上一篇中,我们分析了Volley的一个总体组成。今天我们继续分析Volley的一个数据流走向,即从初始化到发起请求,再到请求结束的一个流程。
先看初始化。
Volley的初始化,实际上就是返回一个RequestQueue的队列。在Volley中调用。一个最简单的创建方式即有一个Context即可。
/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
*
* @param context A {@link Context} to use for creating the cache dir.
* @return A started {@link RequestQueue} instance.
*/
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
我们的分析当然不是点到即止,所以,我们重点关注参数最全的构造函数。
/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
* You may set a maximum size of the disk cache in bytes.
*
* @param context A {@link Context} to use for creating the cache dir.
* @param stack An {@link HttpStack} to use for the network, or null for default.
* @param maxDiskCacheBytes the maximum size of the disk cache, in bytes. Use -1 for default size.
* @return A started {@link RequestQueue} instance.
*/
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
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 {
// 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);
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.start();
return queue;
}
这个方法有三个参数Context,HttpStack和maxDiskCacheBytes.
前面说了HttpStack是一个接口,用于Http请求的具体实现,maxDiskCacheBytes用于创建磁盘缓存时来设置最大的缓存容量。
初始化中有意思的是自定义了一个USERAGENT,这个USERAGENT用在HTTP头部中,表示当前的请求是由当前软件发起,方便日后查找问题和统计。这里也建议自己在创建网络请求的时候,用自己的UA代替。按照经验,UA通常由软件(框架)名称+版本+其他附加信息构成。
创建HTTP请求这里也区别开了SDK VERSION 在9以上和9以下。
对于9以上的版本,直接使用了HttpURLConnection
,对于9以下的版本使用了AndroidHttpClient
,什么原因呢,可以参考:http://android-developers.blogspot.com/2011/09/androids-http-clients.html
由于这文章需要翻墙,我这里把要点罗列如下:
1.
HttpURLConnection
是一个轻量级的Http连接方案,适用于大多数应用,它支持流压缩与网络缓存,能够减少网络流量与节省电池,因此推荐Gingerbread 及以上的使用这个API。
2. 在Gingerbread以下,
HttpURLConnection
当在一个还没有读完的流上调用close方法时,这个流上没有读完的部分会缓存下来,然后会加到下一个请求的数据区,导致下一个请求的数据错误。3.
AndroidHttpClient
复杂扩展性好且稳定,但AndroidTeam很难在保证兼容性的同时改进一些特性,因此,通常不推荐使用。
当然如果你是自己扩展之后,传的一个实现了HttpStack的对象,就不会走到上面的逻辑中。
在根据版本获取了HttpStack对象后,将其作为参数传给Network的默认实现BasicNetwork对象。即BasicNetwork中实际上是对HttpStack的一个包装(当然,如果自己实现了其他的Network,就不一定需要这个HttpStack)。
之后,根据maxDiskCacheBytes创建了一个磁盘缓存。
最后,启动这个RequestQueue。
启动时做了两件事,创建CacheDispatcher和NetworkDispatcher。这两个都是线程类的子类,CacheDispatcher 的线程主要在作用是本地的IO操作,加载缓存用。NetworkDispatcher的线程主要执行网络操作,默认创建了1个缓存线程和4个网络线程。
在初始化完成之后,就可以使用RequestQueue来发起网络请求。
发起请求从RequestQueue的add方法开始。
http://img.blog.csdn.net/20150425163836789
流程如上图,是比如简单的。这里说几个有趣细节。
在add的第一行代码,我们看到 request把RequestQueue传到了对象中,并作为一个成员变量保存。那为什么?因为request的生命周期是由request的一些方法来维护,此后就与这个queue无关了,但是在一个request又需要在结束时,把自己从queue中移除掉,它需要 这个引用来做移除操作。可以参见request.finish的代码。
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
每个请求,有一个唯一的序列号,这个是用AtomicInteger来实现的。
private AtomicInteger mSequenceGenerator = new AtomicInteger();
/**
* Gets a sequence number.
*/
public int getSequenceNumber() {
return mSequenceGenerator.incrementAndGet();
}
在没有主动设置优先级的情况,这个是做为request的优先级来处理的。即数字越小,优先级越高。
还有个有意思的点是request的日志系统,所有的状态改变,都会有事件记录,以便跟踪问题。
从上图可以看出,比如缓存命中情况,网络请求情况,重试情况等,都有一一记录。
volley避免重复请求节约网络资源的逻辑写得很有技巧。这里巧妙的利用了一个空的value来处理,可以少生成一个链表对象。
// 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);
}
如果是第一次请求,直接放入一个空value,第二次如果是相同的请求,才会增加一个链表来缓存相同的request。当第一个请求网络完成后,它才会把这个链表中的请求通通加到缓存队列中。这样,即可以保证即时很短时间内的并发相同请求,实际只有一个才会使用网络资源,又能保证相同的请求不会被丢弃。
volley的很多细节还是很值得我们学习。下一次我们对一些重要的类进行分析。