Volley是一个Android HTTP库,只支持异步方式。
发送请求样例
final TextView mTextView = (TextView) findViewById(R.id.text);
...
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Display the first 500 characters of the response string.
mTextView.setText("Response is: "+ response.substring(0,500));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mTextView.setText("That didn't work!");
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
发送请求源码分析
一个请求的生命周期如下图:
从上图可以看出,在Volley中会有三个线程:UI线程负责发请求和收响应;缓存分发器;网络分发器。
Volley类中只负责一件事情,就是创建一个RequestQueue对象,用于存放请求,该对象最好是单例的,供整个APP使用。
其具体实现如下:
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) {
}
//对HttpStack赋值
if (stack == null) {
//如果SDK大于8,使用HurlStack,否则使用HttpClientStack
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对象
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
RequestQueue的构造方法中有两个参数,一个是Cache,负责将响应保存到磁盘,一个是Network,负责执行HTTP操作。
下面先看一下RequestQueue的内部定义,RequestQueue内部主要有四个字段是在初始化的时候指定的,分别是:
private final Cache mCache;
private final Network mNetwork;
/** 分发HTTP响应 */
private final ResponseDelivery mDelivery;
/** 网络分发器 */
private NetworkDispatcher[] mDispatchers;
RequestQueue的构造方法一共有三个,但最终都会调用下面的这个构造器,如下:
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
RequesteQueue#start()
当创建好RequestQueue之后,调用了start()方法,start()方法如下:
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// 创建缓存分发器,然后启动
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();
}
}
可以看到RequestQueue的start方法就是启动其内部的分发器,主要包括一个缓存分发器和多个网络分发器,网络分发器的数量实在构造方法中设置的,默认为4个。
前面的例子中,当创建好Request和RequestQueue之后,就将Request放进RequestQueue中就可以了。
RequestQueue#add()方法
add()方法实现如下:
public <T> Request<T> add(Request<T> request) {
//Request和RequestQueue关联
request.setRequestQueue(this);
//将请求加入到Set中
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// 如果这个请求不应该被缓存,那么直接添加进网络队列中
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
//如果该请求已经在执行了
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 {
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
这里面涉及到RequestQueue中的多个个集合,定义分别如下:
//如果当前请求已经在执行了,那么将会加入到该集合中
private final Map<String, Queue<Request<?>>> mWaitingRequests =
new HashMap<String, Queue<Request<?>>>();
//当前正在执行的请求
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
//缓存请求优先级队列
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
//网络请求优先级队列
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
可以看到,add()方法主要做的就是根据不同情况将请求加入到不同的队列中。由于缓存请求队列和网络请求队列都是使用的优先级队列,所以可以给Request设置优先级。
1. 如果请求不应该从缓存中得到,那么直接加入到网络请求队列中;
2. 如果请求已经在执行了,那么将其加入到正在执行的一个HashMap中,其中key是请求,值是一个请求的队列;
3. 如果请求没有在执行,那么将其加入到缓存队列中。
在这里,我们已经将一个请求提交了,下面看一下,Request的缓存键值是如何得到的。
Request#getCacheKey()
public String getCacheKey() {
return getUrl();
}
public String getUrl() {
return mUrl;
}
可以看到一个Request的键值就是其URL。
现在考虑一个问题:创建了同一个相同URL的两个Request,或者说同一个Request添加到RequestQueue两次,情况是怎么样的呢?下面分别分析:
RequestQueue添加同一相同URL的两个Request对象
设为Request1和Request2,添加时,由于Request没有重写equals方法和hashCode方法,所以mCurrentRequests会人为这是两个不同的请求,都添加进Set,然后比如说Request1首先获得了mWaitingRequests的锁,由于mWaitingRequests中还没有该URL,所以被添加进mWaitingRequests和放到了缓存队列中,然后当Request2再获取到mWaitingRequests时候,由于已经有了URL,所以会在mWaitingRequests中创建一个链表并把该请求放入链表中,从而可以看出同一时刻相同URL的请求只会被执行一次。不过具体放在链表中的请求在Request1被处理之后是如何处理的,下面一篇博客会分析到。
Request添加同一相同的Request两次
经过前面的分析,可以知道,在mCurrentRequests中后一个Request会代替前一个Request,而后一个Request会被放入mWaitingRequests的链表中。可以发现同一个Request对象即在缓存队列中,又在待处理的队列中。
下面就Request被执行完之后,看是怎样操作的来解释上面两个问题。
结束请求源码分析
当想取消一个Request的执行时,可以调用RequestQueue的finish()来主要取消执行,也可以在Request被正常执行完之后自己调用finish()方法,下面先从Request的finish()方法看起,其实现如下:
Request#finish()
void finish(final String tag) {
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
if (MarkerLog.ENABLED) {
final long threadId = Thread.currentThread().getId();
if (Looper.myLooper() != Looper.getMainLooper()) {
// If we finish marking off of the main thread, we need to
// actually do it on the main thread to ensure correct ordering.
Handler mainThread = new Handler(Looper.getMainLooper());
mainThread.post(new Runnable() {
@Override
public void run() {
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
}
});
return;
}
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
} else {
long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;
if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {
VolleyLog.d("%d ms: %s", requestTime, this.toString());
}
}
}
可以看到,首先也是调用了RequestQueue的finish()方法,下面再来分析RequestQueue的finish()方法。
RequestQueue#finish()
void finish(Request<?> request) {
// 首先从当前执行集合中删除
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
//如果请求应该被缓存
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
//得到请求的键值,即URL
String cacheKey = request.getCacheKey();
//得到与URL关联的等待队列
Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
//如果队列不为null
if (waitingRequests != null) {
//将所有请求都加入到缓存队列中
mCacheQueue.addAll(waitingRequests);
}
}
}
}
这儿,可以看到,首先是从Set集合中删除当前请求,然后判断请求是否应该被缓存,这和add()方法里面是相一致的,add()方法里只有请求允许使用缓存,才会被加入到URL关联的等待队列中。然后就是将与URL关联的请求都加入到缓存队列中。下面就上面两个问题再次给出解释。
1. RequestQueue添加同一相同URL的两个Request对象:对于这种情况,Request1被执行,Request2被存在等待队列中,当Request1执行完成后,将会从URL关联的队列中得到Request2对象,然后将Request2加入到缓存队列,由于Request1之前已经有缓存结果了,所以执行Request2时只需要经过CacheDispatcher就可以得到结果然后finish了,可以发现这种情况下,同一个URL的只会进行一次网络请求,其余的都是走缓存请求;不过这是针对于可以缓存响应的情况,如果不能缓存响应,那么都会直接加入到网络请求中执行两次网络操作。
2. Request添加同一相同的Request两次:当执行了第一次之后,就从Set中成功移除了Request,然后再从等待队列中取出,加入到缓存队列,可以当发现这一次的Request依然会走CacheDispatcher中一趟。
至此,我们将一个请求提交给了RequestQueue,那么RequestQueue是如何执行请求,又是如何将响应交付给UI线程处理呢?这一部分,我们下一篇再讲。下一篇文章Volley源码解析(二)——CacheDispatcher将会介绍CacheDispatcher是如何进行缓存分发的。