Volley源码解析(一)——发送请求与结束请求
2017-05-02 09:09
429 查看
Volley是一个Android HTTP库,只支持异步方式。
从上图可以看出,在Volley中会有三个线程:UI线程负责发请求和收响应;缓存分发器;网络分发器。
Volley类中只负责一件事情,就是创建一个RequestQueue对象,用于存放请求,该对象最好是单例的,供整个APP使用。
其具体实现如下:
RequestQueue的构造方法中有两个参数,一个是Cache,负责将响应保存到磁盘,一个是Network,负责执行HTTP操作。
下面先看一下RequestQueue的内部定义,RequestQueue内部主要有四个字段是在初始化的时候指定的,分别是:
RequestQueue的构造方法一共有三个,但最终都会调用下面的这个构造器,如下:
可以看到RequestQueue的start方法就是启动其内部的分发器,主要包括一个缓存分发器和多个网络分发器,网络分发器的数量实在构造方法中设置的,默认为4个。
前面的例子中,当创建好Request和RequestQueue之后,就将Request放进RequestQueue中就可以了。
这里面涉及到RequestQueue中的多个个集合,定义分别如下:
可以看到,add()方法主要做的就是根据不同情况将请求加入到不同的队列中。由于缓存请求队列和网络请求队列都是使用的优先级队列,所以可以给Request设置优先级。
1. 如果请求不应该从缓存中得到,那么直接加入到网络请求队列中;
2. 如果请求已经在执行了,那么将其加入到正在执行的一个HashMap中,其中key是请求,值是一个请求的队列;
3. 如果请求没有在执行,那么将其加入到缓存队列中。
在这里,我们已经将一个请求提交了,下面看一下,Request的缓存键值是如何得到的。
可以看到一个Request的键值就是其URL。
现在考虑一个问题:创建了同一个相同URL的两个Request,或者说同一个Request添加到RequestQueue两次,情况是怎么样的呢?下面分别分析:
下面就Request被执行完之后,看是怎样操作的来解释上面两个问题。
可以看到,首先也是调用了RequestQueue的finish()方法,下面再来分析RequestQueue的finish()方法。
这儿,可以看到,首先是从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是如何进行缓存分发的。
发送请求样例
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是如何进行缓存分发的。
相关文章推荐
- 【安卓网络请求开源框架Volley源码解析系列】初识Volley及其基本用法
- 【安卓网络请求开源框架Volley源码解析系列】定制自己的Request请求及Volley框架源码剖析
- 【安卓网络请求开源框架Volley源码解析系列】初识Volley及其基本用法
- Volley源码解析(一):网络请求内容
- 【安卓网络请求开源框架Volley源码解析系列】定制自己的Request请求及Volley框架源码剖析
- Volley网络请求框架源码解析
- 【安卓网络请求开源框架Volley源码解析系列】初识Volley及其基本用法
- Volley源码解析<三> Request请求
- Volley源码解析(三)网络请求流程
- Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析
- boa源码解析(1)-接收请求,发送html的流程
- 4、Volley解析(二),源码的深入分析一,缓存线程和网络请求线程
- 【安卓网络请求开源框架Volley源码解析系列】初识Volley及其基本用法
- 【安卓网络请求开源框架Volley源码解析系列】定制自己的Request请求及Volley框架源码剖析
- Volley源码解析<四> RequestQueue请求队列
- Android短彩信源码解析-短信发送流程(二)
- Android短彩信源码解析-短信发送流程(三)
- Android短彩信源码解析-短信发送流程(三)
- Android Volley完全解析(四),带你从源码的角度理解Volley
- volley 发送post请求