Android Volley源码分析(2)
来源:互联网 发布:中融财富网络贷款 编辑:程序博客网 时间:2024/05/16 11:09
这篇博客我们继续分析一下Volley框架的源码。
之前的博客侧重于RequestQueue启动后服务端的运行流程,
本篇博客主要分析一下加入Request后,RequestQueue具体的处理方式。
1、 RequestQueue的add接口
我们从RequestQueue的add接口入手:
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. // 将Request与RequestQueue关联起来,毕竟用户是可以创建多个RequestQueue的 request.setRequestQueue(this); synchronized (mCurrentRequests) { //mCurrentRequests用于保留RequestQueue正在处理的Request //当Request处理完毕后,回调RequestQueue的finish接口,就可以被mCurrentRequests移除了 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. if (!request.shouldCache()) { //对于无需Cache的Request,直接加入到网络队列中,进行下载操作 mNetworkQueue.add(request); return request; } // Insert request into stage if there's already a request with the same cache key in flight. // mWaitingRequests相当于是RequestQueue的运行时缓存 synchronized (mWaitingRequests) { //Request的getCacheKey是可以重载的,默认使用的Request的url String cacheKey = request.getCacheKey(); //如果之前已经发送过同样url的Request,且这个Request正在被处理 if (mWaitingRequests.containsKey(cacheKey)) { // There is already a request in flight. Queue up. Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); //用LinkedList保存重复的请求,不再触发后续下载操作 if (stagedRequests == null) { stagedRequests = new LinkedList<Request<?>>(); } stagedRequests.add(request); //当一个Request处理完毕,回调RequestQueue的finish接口后, //其对应的stagedRequests将从mWaitingRequests移除 //全部被添加到mCacheQueue中 //于是,如果之前的Request已经下载成功,有了cache信息,就不会再触发下载操作 //否则,Volley框架会重新进行下载 mWaitingRequests.put(cacheKey, stagedRequests); .................... } else { // Insert 'null' queue for this cacheKey, indicating there is now a request in // flight. // 非重复的请求,会立即加入到CacheQueue中 // 根据之前博客的分析,我们知道CacheDispatcher将在物理缓存中, // 进一步查找是否有该请求对应的回复信息 mWaitingRequests.put(cacheKey, null); mCacheQueue.add(request); } return request; }}
从RequestQueue的add接口来看,其中比较值得一提的是引入了mWaitingRequests。
当访问同一个url的Request连续加入到RequestQueue时,只有第一个会立即被加入到mCacheQueue中进行处理,
其它后续的Request均被加入到mWaitingRequests中。
当第一个Request处理完毕回调Request的finish接口后,代码如下:
<T> void finish(Request<T> request) { // Remove from the set of requests currently being processed. // 首先从mCurrentRequests移除该Request synchronized (mCurrentRequests) { mCurrentRequests.remove(request); } //回调listener对应的接口 synchronized (mFinishedListeners) { for (RequestFinishedListener<T> listener : mFinishedListeners) { listener.onRequestFinished(request); } } if (request.shouldCache()) { synchronized (mWaitingRequests) { String cacheKey = request.getCacheKey(); //取出waitingRequests中Request对应的后续请求队列 Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey); if (waitingRequests != null) { ............... // Process all queued up requests. They won't be considered as in flight, but // that's not a problem as the cache has been primed by 'request'. // 将后续请求队列中的Request全部加入到CacheQueue中,供CacheDispatcher处理 // 如上文所述,若之前下载成功,CacheDispatcher就会从cache中获取到结果 // 否则,将重新触发下载 // 需要注意的是,如果请求队列中有多个Request,将被NetworkDispatcher并发处理 mCacheQueue.addAll(waitingRequests); } } } }
可以看出,RequestQueue通过mWaitingRequests,可以在一定程度上避免对同一个网络地址的重复访问。
2、BasicNetwork的performRequest接口
根据之前博客分析的CacheDispatcher和NetworkDispatcher的代码,
我们知道一个Request如果没有对应的缓存信息,
最终将被NetworkDispatcher交给BasicNetwork的performRequest函数处理。
在performRequest函数中,将进行实际的下载操作。
现在,我们来看看这部分代码:
@Overridepublic NetworkResponse performRequest(Request<?> request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime(); //此处while参数为true,必须返回结果或抛出异常才能结束 //这么设计是为了便于重新下载 while (true) { //以下均是用于保存返回结果的 HttpResponse httpResponse = null; byte[] responseContents = null; Map<String, String> responseHeaders = Collections.emptyMap(); try { // Gather headers. // 将用于保存http头部信息 Map<String, String> headers = new HashMap<String, String>(); //这部分代码对应于有cache,但需要重新更新信息的场景 //如果有Cache,将Cache中的信息加入到headers中 //其中比较重要的是,增加了"If-Modified-Since"字段,即要求从服务器获取xxx时间之后的数据 addCacheHeaders(headers, request.getCacheEntry()); //利用HttpStack进行实际的下载,得到httpResponse httpResponse = mHttpStack.performRequest(request, headers); //在这之后的代码比较繁杂,但主体的意思就是根据Response中的信息,进行相应的处理 // Http Response中不同的statusCode // 定义了网络访问后不同的情况 // 即用来说明网络访问是否成功、或者表明了网络访问失败的原因等 StatusLine statusLine = httpResponse.getStatusLine(); int statusCode = statusLine.getStatusCode(); //将Response中的头部信息,按键值对存入到前文定义的responseHeaders中 responseHeaders = convertHeaders(httpResponse.getAllHeaders()); // Handle cache validation. // 网络访问返回304,说明该Request对应的Cache还是可以用的,服务端对应的资源并没有更新 if (statusCode == HttpStatus.SC_NOT_MODIFIED) { Entry entry = request.getCacheEntry(); if (entry == null) { //构造Response返回 return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null, responseHeaders, true, SystemClock.elapsedRealtime() - requestStart); } // A HTTP 304 response does not have all header fields. We // have to use the header fields from the cache entry plus // the new ones from the response. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 // 注释写的还是很清楚的,合并形成一个Response Header entry.responseHeaders.putAll(responseHeaders); //构造Response返回 return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data, entry.responseHeaders, true, SystemClock.elapsedRealtime() - requestStart); } // Some responses such as 204s do not have content. We must check. // 非304场景,保存httpResponse中的数据 if (httpResponse.getEntity() != null) { responseContents = entityToBytes(httpResponse.getEntity()); } else { // Add 0 byte response as a way of honestly representing a // no-content request. responseContents = new byte[0]; } ............ //网络返回结果失败,主动抛出IO异常,后文处理 if (statusCode < 200 || statusCode > 299) { throw new IOException(); } //正常情况,构造Response返回 return new NetworkResponse(statusCode, responseContents, responseHeaders, false, SystemClock.elapsedRealtime() - requestStart); //网络下载可能碰到的异常比较多,显得比较繁杂 } catch (SocketTimeoutException e) { //attemptRetryOnException是用于判断是否还需要重试 //如果不需要重试,就会抛出参数中的Error信息,结束performRequest函数 //否则,就会回到while循环的起始部分,重新下载 attemptRetryOnException("socket", request, new TimeoutError()); } catch (ConnectTimeoutException e) { attemptRetryOnException("connection", request, new TimeoutError()); } catch (MalformedURLException e) { throw new RuntimeException("Bad URL " + request.getUrl(), e); } catch (IOException e) { //前文status code异常时,主动抛出了IOException int statusCode = 0; NetworkResponse networkResponse = null; if (httpResponse != null) { statusCode = httpResponse.getStatusLine().getStatusCode(); } else { throw new NoConnectionError(e); } ..................... if (responseContents != null) { networkResponse = new NetworkResponse(statusCode, responseContents, responseHeaders, false, SystemClock.elapsedRealtime() - requestStart); //401 Unauthorized 客户试图未经授权访问受密码保护的页面 // 403 Forbidden 资源不可用 if (statusCode == HttpStatus.SC_UNAUTHORIZED || statusCode == HttpStatus.SC_FORBIDDEN) { attemptRetryOnException("auth", request, new AuthFailureError(networkResponse)); } else { // TODO: Only throw ServerError for 5xx status codes. // 这里抛出的异常,都会被NetworkDispatcher捕获,封装成VolleryError发送给UI线程 throw new ServerError(networkResponse); } } else { throw new NetworkError(networkResponse); } } }}
BasicNetwork的performRequest看起来比较复杂,但它的逻辑其实还是比较简单的:
根据Http访问得到返回值中的status code,判断网络访问的结果,并将结果封装成NetworkResponse递交给UI线程。
此外,在某些错误场景下,BasicNetwork将调用attemptRetryOnException函数判断是否需要进行重传操作,
或者直接抛出异常让NetworkDispatcher捕获,形成递交给UI线程的VolleyError。
在进一步分析HttpStack的下载过程前,我们先来看看上文提到的attemptRetryOnException函数:
private static void attemptRetryOnException(String logPrefix, Request<?> request, VolleyError exception) throws VolleyError { //从Request中获取Retry Policy RetryPolicy retryPolicy = request.getRetryPolicy(); int oldTimeout = request.getTimeoutMs(); try { // 调用对应的retry接口,更新重传次数 // 超过重传上限,抛出异常 retryPolicy.retry(exception); } catch (VolleyError e) { request.addMarker( String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout)); // 捕获retry接口抛出的异常后,再次抛出异常 // 结束BasicNetwork的performRequest函数,返回错误信息给UI线程 throw e; } request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));}
目前Volley中原生的Request使用的均是DefaultRetryPolicy,我们看看对应的接口实现:
................. @Override public void retry(VolleyError error) throws VolleyError { mCurrentRetryCount++; mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); // hasAttemptRemaining判断能否继续重传 if (!hasAttemptRemaining()) { throw error; } } /** * Returns true if this policy has attempts remaining, false otherwise. */ protected boolean hasAttemptRemaining() { //原生的设计是判断重传次数是否超过限制 //mMaxNumRetries的值默认设计为1,即仅能重传一次 return mCurrentRetryCount <= mMaxNumRetries; } ..............
不难看出,目前attemptRetryOnException函数主要根据当前Request的重传次数及上限,
来判断是否可以进行一次下载操作。
3、HttpStack的performRequest接口
从前文的代码可以看出,BasicNetwork最终的下载操作依赖于它的HttpStack。
根据之前博客的分析,我们知道Volley在创建RequestQueue时,
生成了HurlStack和HttpClientStack,并且在Android的高版本中将使用HurlStack。
因此,我们就以HurlStack的performRequest为例,看看下载的具体操作。
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { String url = request.getUrl(); //map中保存头信息 HashMap<String, String> map = new HashMap<String, String>(); map.putAll(request.getHeaders()); map.putAll(additionalHeaders); if (mUrlRewriter != null) { //按需对url进行重写 String rewritten = mUrlRewriter.rewriteUrl(url); if (rewritten == null) { throw new IOException("URL blocked by rewriter: " + url); } url = rewritten; } URL parsedUrl = new URL(url); //创建URLConnection,后文分析openConnection HttpURLConnection connection = openConnection(parsedUrl, request); //添加头部附加信息 for (String headerName : map.keySet()) { connection.addRequestProperty(headerName, map.get(headerName)); } //根据Request,进一步添加信息,后文再进一步看看这个函数 setConnectionParametersForRequest(connection, request); // Initialize HttpResponse with data from the HttpURLConnection. ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); //利用HttpURLConnection的getResponseCode方法得到网络访问的返回的status code int responseCode = connection.getResponseCode(); if (responseCode == -1) { // -1 is returned by getResponseCode() if the response code could not be retrieved. // Signal to the caller that something was wrong with the connection. throw new IOException("Could not retrieve response code from HttpUrlConnection."); } //将结果封装成org.apache.http.message.BasicHttpResponse的格式 StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage()); BasicHttpResponse response = new BasicHttpResponse(responseStatus); // 根据Request Method及response code,判断是否还有数据需要下载 if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) { // 若有数据待下载,则调用entityFromConnection获取数据,并封装到response中 // 后文再来进一步分析这个函数 response.setEntity(entityFromConnection(connection)); } //将Http的头部信息,写入到response中 for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { if (header.getKey() != null) { Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); response.addHeader(h); } } return response;}
通过上文的代码,我们终于明白了,Volley的底层通信实际上依赖的是HttpURLConnection。
而HttpURLConnection实际上是通过Socket建立实际通信链接的。
之后,我们在单独利用一篇博客专门分析一下HttpURLConnection的通信流程。
现在,我们先来看看HttpStack的performRequest中调用的几个函数。
openConnection
openConnection函数负责根据Request中的信息,建立一个HttpURLConnection,其源码如下:
private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException { // 利用URL的openConnection接口创建HttpURLConnection,并设置了Follow Redirects属性 HttpURLConnection connection = createConnection(url); // 配置HttpURLConnection的一些属性 int timeoutMs = request.getTimeoutMs(); connection.setConnectTimeout(timeoutMs); connection.setReadTimeout(timeoutMs); connection.setUseCaches(false); connection.setDoInput(true); // use caller-provided custom SslSocketFactory, if any, for HTTPS // 默认的HurlStack并没有设置SslSocketFactory if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) { //HttpURLConnection的SocketFactory将负责创建出实际通信用的Socket //之后的博客分析HttpURLConnection的通信流程时,再来分析这些细节 ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory); } return connection; }
从上述代码可以看出,openConnection函数主要负责开启HttpURLConnection,并设置一些必要的参数。
setConnectionParametersForRequest
从函数名即可看出,该函数将用于进一步为HttpURLConnection设置参数,我们稍微看看细节。
static void setConnectionParametersForRequest(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError { switch (request.getMethod()) { //这个Method被deprecated了,根据post body,来决定到底是get方法还是post方法 case Method.DEPRECATED_GET_OR_POST: // This is the deprecated way that needs to be handled for backwards compatibility. // If the request's post body is null, then the assumption is that the request is // GET. Otherwise, it is assumed that the request is a POST. byte[] postBody = request.getPostBody(); //如果是post方法,则直接写入DataOutputStream if (postBody != null) { // Prepare output. There is no need to set Content-Length explicitly, // since this is handled by HttpURLConnection using the size of the prepared // output stream. connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getPostBodyContentType()); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(postBody); out.close(); } break; //下文的方法,除了post、put和patch外,都只是修改HttpURLConnection的Method参数 //post、put和patch均会利用addBodyIfExists函数,进一步写入信息 case Method.GET: // Not necessary to set the request method because connection defaults to GET but // being explicit here. connection.setRequestMethod("GET"); break; case Method.DELETE: connection.setRequestMethod("DELETE"); break; case Method.POST: connection.setRequestMethod("POST"); addBodyIfExists(connection, request); break; case Method.PUT: connection.setRequestMethod("PUT"); addBodyIfExists(connection, request); break; case Method.HEAD: connection.setRequestMethod("HEAD"); break; case Method.OPTIONS: connection.setRequestMethod("OPTIONS"); break; case Method.TRACE: connection.setRequestMethod("TRACE"); break; case Method.PATCH: connection.setRequestMethod("PATCH"); addBodyIfExists(connection, request); break; default: throw new IllegalStateException("Unknown method type."); } }
我们看看addBodyIfExists函数的代码:
//容易看出,Method.DEPRECATED_GET_OR_POST的处理一致,就是判断是否有需上传的数据 //如果有数据的话,就写入DataOutputStream中 private static void addBodyIfExists(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError { byte[] body = request.getBody(); if (body != null) { connection.setDoOutput(true); connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType()); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(body); out.close(); } }
entityFromConnection
entityFromConnection主要用于获取HttpResponse中数据,其源码如下:
private static HttpEntity entityFromConnection(HttpURLConnection connection) { BasicHttpEntity entity = new BasicHttpEntity(); InputStream inputStream; try { //实际上就是获取HttpURLConnection的InputStream //然后从InputStream中的到信息,填充到HttpEntity中 inputStream = connection.getInputStream(); } catch (IOException ioe) { inputStream = connection.getErrorStream(); } entity.setContent(inputStream); entity.setContentLength(connection.getContentLength()); entity.setContentEncoding(connection.getContentEncoding()); entity.setContentType(connection.getContentType()); return entity; }
至此,HurlStack的下载方式基本分析完毕,可以看出HurlStack主要根据Http头部的一些标志,
利用HttpURLConnection来完成实际的上传和下载工作。
4、ExecutorDelivery返回结果给UI线程
截至到这里,我们已经分析了Volley服务端处理Request的流程,也明白了Volley具体的下载操作,
现在是时候看看Volley框架如何将结果返回给UI线程了。
根据之前的代码,我们知道Volley在创建RequestQueue时,在RequestQueue的构造函数中创建了ExecutorDelivery。
ExecutorDelivery中封装了主线程对应的Handler。
我们看看ExecutorDelivery的构造函数:
public ExecutorDelivery(final Handler handler) { // Make an Executor that just wraps the handler. mResponsePoster = new Executor() { @Override public void execute(Runnable command) { //Executor将实际的工作交给主线程的Handler处理 handler.post(command); } }; }
根据之前的代码,无论是CacheDispatcher还是NetworkDispatcher,
在处理完NetworkRequest后,均会调用ExecutorDelivery的postResponse接口发送处理结果,
或者利用postError接口发送错误信息。
我们一起来看一下ExecutorDelivery的函数:
@Override public void postResponse(Request<?> request, Response<?> response) { postResponse(request, response, null); } @Override public void postResponse(Request<?> request, Response<?> response, Runnable runnable) { request.markDelivered(); request.addMarker("post-response"); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); } @Override public void postError(Request<?> request, VolleyError error) { request.addMarker("post-error"); Response<?> response = Response.error(error); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null)); }
从这段代码可以看出,最终将在主线程中运行ResponseDeliveryRunnable的run方法:
@Override public void run() { // If this request has canceled, finish it and don't deliver. if (mRequest.isCanceled()) { mRequest.finish("canceled-at-delivery"); return; } // Deliver a normal response or error, depending. if (mResponse.isSuccess()) { mRequest.deliverResponse(mResponse.result); } else { mRequest.deliverError(mResponse.error); } // If this is an intermediate response, add a marker, otherwise we're done // and the request can be finished. if (mResponse.intermediate) { mRequest.addMarker("intermediate-response"); } else { mRequest.finish("done"); } // If we have been provided a post-delivery runnable, run it. if (mRunnable != null) { mRunnable.run(); } }
容易看出,上文均是回调Request的接口,这些接口就可以由其子类来实现。
例如,StringRequest的deliverResponse函数:
@Override protected void deliverResponse(String response) { //回调Listener的onResponse接口 mListener.onResponse(response); }
至此,Volley框架的主要流程分析完毕,其它的ImageLoader、NetworkImageView均是在当前框架的RequestQueue、ImageRequest
基础上做到进一步封装,就不做进一步分析了。
5、Google提供的原理图
最后,结合Google提供的Volley原理图,我们一起回顾一下整个Volley的工作流程。
如上图所示,整个Volley框架共有三类线程。
主线程的工作主要是创建和启动RequestQueue的各组件,
然后由封装主线程Handler的ExecutorDelivery传递信息。
缓存查找线程的工作主要由CacheDispatcher来完成。
CacheDispatcher将在物理文件中查找是否有Request对应的信息,
如果能够查找到信息,就会利用ExecutorDelivery向UI线程返回结果。
否则,将Request递交给NetworkDispatcher处理。
Volley中默认会有4个NetworkDispatcher,
它们将从支持并发访问的BlockingQueue中获取Request进行处理。
NetworkDispatcher主要以HttpURLConnection来进行实际的下载操作,
完成下载工作后,同样通过ExecutorDelivery向UI线程返回结果,
并按照需要将结果写入到物理缓存中。
ExecutorDelivery将在主线程中,回调Request的接口,
这些接口将由Request的子类来实现。
从整体上来讲,理解Volley框架还是比较简单的,
其关键的特点就是缓存、并发,当然Volley本身还会进行一些重传操作。
- Android Volley源码分析(2)
- Android-Volley源码分析
- [Android]volley源码分析
- Android-Volley源码分析
- android Volley 源码分析
- Volley(2)源码分析
- android-----Volley框架源码分析
- android-----Volley框架源码分析
- android Volley的源码分析
- Android Volley源码分析(1)
- 【Android框架】volley源码分析
- 【进阶android】Volley源码分析——Volley的流程
- 【进阶android】Volley源码分析——Volley的线程
- 【进阶android】Volley源码分析——Volley的缓存
- [Android] Volley源码分析(一)体系结构
- [Android]Volley源码分析(二)Cache
- [Android]Volley源码分析(叁)Network
- [Android]Volley源码分析(肆)应用
- DS.Lab筆記
- 摘自w3school的html标签内容——图像标签
- 一些JS题
- CSS样式中部分伪类选择器的应用
- 深入理解Java虚拟机——Java内存区域
- Android Volley源码分析(2)
- java实现silk音频文件转换成mp3
- 使用pageoffice 编辑文档和转pdf碰到的问题与解决方法
- Linux块设备IO子系统(二) _页高速缓存
- Integer to Roman
- Python导入自己写的模块
- iOS开发-删除已经配置的类库和移除CocoaPods
- Spring + JDBC 组合开发集成步骤
- 2017.3.23考试总结