您的位置:首页 > 移动开发 > Android开发

Volley源码解析(一)——发送请求与结束请求

2017-05-02 09:09 429 查看
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是如何进行缓存分发的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息