本文是Volley源码浅析一的续文
NetworkDispatcher
-
上文说到如果Request不需要缓存或者缓存的内容已经过期,又或者Ttl不过期,但是softTtl已经超出了当前时间这三种情况最后会把当前Request加入到网络请求队列,那么4个NetworkDispatcher线程中的其中某个线程就能够从阻塞队列中取出Request脱离阻塞,本文都围绕下面这段代码
// NetworkDispatcher.java private void processRequest() throws InterruptedException { Request<?> request = mQueue.take(); processRequest(request); } void processRequest(Request<?> request) { long startTimeMs = SystemClock.elapsedRealtime(); try { request.addMarker("network-queue-take"); // 如果请求在加入到网络请求队列前就已经取消了,结束这个请求 if (request.isCanceled()) { request.finish("network-discard-cancelled"); // 如果该请求是从缓存线程添加到网络请求队列中的并且没有相同缓存key的请求正在等待响应那么内部回调 // WaitingRequestManager.onNoUsableResponseReceived(), 该方法会移除当前请求,并判断 // 是否还有相同key的请求如果有则将该请求取出放入网络请求队列 request.notifyListenerResponseNotUsable(); return; } // 统计流量 addTrafficStatsTag(request); // 执行网络请求逻辑 mNetwork为BasicNetwork(new HurlStack())该方法要么返回要么就抛出异常 // NetworkDispatcher -> BasicNetwork -> AdaptedHttpStack -> HurlStack NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete"); // 如果返回304并且这个该响应已经被分发过,不应该再次分发同样的响应,主要是用于那些Ttl没超时,而 // softTtl已经超时需要刷新的请求,因为框架会直接把缓存的响应返回,然后再将该请求放置到网络请求队列 // 再次请求,对于这种请求返回了304,当然不需要再分发了,所有对于这种请求如果这次请求返回的不是304 // 那么Request.parseNetworkResponse会被回调两次 if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); request.notifyListenerResponseNotUsable(); return; } // 解析响应 Response<?> response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); // 如果请求需要缓存那么将响应缓存 if (request.shouldCache() && response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } // 标志请求已经被分发,其实ExecutorDelivery内部postResponse也会调用request.markDelivered() request.markDelivered(); mDelivery.postResponse(request, response); // 回调NetworkRequestCompleteListener.onResponseReceived request.notifyListenerResponseReceived(response); } catch (VolleyError volleyError) { volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); parseAndDeliverNetworkError(request, volleyError); request.notifyListenerResponseNotUsable(); } catch (Exception e) { VolleyLog.e(e, "Unhandled exception %s", e.toString()); VolleyError volleyError = new VolleyError(e); volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); mDelivery.postError(request, volleyError); request.notifyListenerResponseNotUsable(); } }
-
来看看Request.notifyListenerResponseNotUsable, 代码中的listener其实就是WaitingRequestManager实例,在调用WaitingRequestManager.maybeAddToWaiting时被设置进去的RequestsWaitingRequestManager这个类是为了避免有相同缓存key的Request还没回来下一个Request就又被加入到网络请求队列中去
void notifyListenerResponseNotUsable() { NetworkRequestCompleteListener listener; synchronized (mLock) { listener = mRequestCompleteListener; } if (listener != null) { listener.onNoUsableResponseReceived(this); } }
-
接着看CacheDispatcher$WaitingRequestManager.onNoUsableResponseReceived
public synchronized void onNoUsableResponseReceived(Request<?> request) { String cacheKey = request.getCacheKey(); List<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey); // 如果只有当前这个请求,那么直接移除,如果还有相同缓存key的下一个请求,那么取出下一个请求将其放入网络请求队列 if (waitingRequests != null && !waitingRequests.isEmpty()) { Request<?> nextInLine = waitingRequests.remove(0); mWaitingRequests.put(cacheKey, waitingRequests); nextInLine.setNetworkRequestCompleteListener(this); try { mCacheDispatcher.mNetworkQueue.put(nextInLine); } catch (InterruptedException iex) { VolleyLog.e("Couldn't add request to queue. %s", iex.toString()); Thread.currentThread().interrupt(); mCacheDispatcher.quit(); } } }
-
接着看看重点 mNetwork.performRequest,这里先给出一个简易流程图
-
先从mNetwork.performRequest说起
// BasicNetwork.java public NetworkResponse performRequest(Request<?> request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime(); while (true) { HttpResponse httpResponse = null; byte[] responseContents = null; List<Header> responseHeaders = Collections.emptyList(); try { // 这里的request.getCacheEntry就是上一篇文章中讲到的如果缓存过期了 // (包括ttl超时或者softTtl超时)就设置request.setCacheEntry(entry); Map<String, String> additionalRequestHeaders = getCacheHeaders(request.getCacheEntry()); // mBaseHttpStack为AdaptedHttpStack(httpStack); httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders); int statusCode = httpResponse.getStatusCode(); responseHeaders = httpResponse.getHeaders(); // 处理缓存确认,如果是304,那么就直接使用缓存的内容(服务端根据客户端的If-None-Match就是响应头 // 里面的Etag、If-Modified-Since就是响应头里面的Last-Modified判断资源没有发生变化从而返回304) if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) { Entry entry = request.getCacheEntry(); if (entry == null) { return new NetworkResponse( HttpURLConnection.HTTP_NOT_MODIFIED, /* data= */ null, /* notModified= */ true, SystemClock.elapsedRealtime() - requestStart, responseHeaders); } // 304状态下的合并响应头,304响应没有包括所有的Header,所以不得不使用缓存里面的响应头加上这次返回响应头 List<Header> combinedHeaders = combineHeaders(responseHeaders, entry); return new NetworkResponse( HttpURLConnection.HTTP_NOT_MODIFIED, entry.data, /* notModified= */ true, SystemClock.elapsedRealtime() - requestStart, combinedHeaders); } // 一些响应比如204、304、HEAD请求没有响应体,必须检查它,防止其被下面的catch代码块判断为内部非手动抛出的IO异常 InputStream inputStream = httpResponse.getContent(); if (inputStream != null) { responseContents = inputStreamToBytes(inputStream, httpResponse.getContentLength()); } else { // 添加一个空数组来代表无响应体的响应 responseContents = new byte[0]; } // 如果请求超过3秒就打印日志 long requestLifetime = SystemClock.elapsedRealtime() - requestStart; logSlowRequests(requestLifetime, request, responseContents, statusCode); if (statusCode < 200 || statusCode > 299) { throw new IOException(); } return new NetworkResponse( statusCode, responseContents, /* notModified= */ false, SystemClock.elapsedRealtime() - requestStart, responseHeaders); } catch (SocketTimeoutException e) { // 就是那个Apache的ConnectTimeoutException attemptRetryOnException("socket", request, new TimeoutError()); } catch (MalformedURLException e) { throw new RuntimeException("Bad URL " + request.getUrl(), e); } catch (IOException e) { int statusCode; if (httpResponse != null) { statusCode = httpResponse.getStatusCode(); } else { throw new NoConnectionError(e); } NetworkResponse networkResponse; // 这里的情况就是上面手动抛出的IO异常 if (responseContents != null) { networkResponse = new NetworkResponse( statusCode, responseContents, /* notModified= */ false, SystemClock.elapsedRealtime() - requestStart, responseHeaders); // 如果是401或者是403就调用RetryPolicy.retry进行重试 if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED || statusCode == HttpURLConnection.HTTP_FORBIDDEN) { attemptRetryOnException( "auth", request, new AuthFailureError(networkResponse)); } else if (statusCode >= 400 && statusCode <= 499) { // 不能重试,其他的客户端错误 throw new ClientError(networkResponse); } else if (statusCode >= 500 && statusCode <= 599) { if (request.shouldRetryServerErrors()) { attemptRetryOnException( "server", request, new ServerError(networkResponse)); } else { throw new ServerError(networkResponse); } } else { // 3xx? No reason to retry. throw new ServerError(networkResponse); } } else { attemptRetryOnException("network", request, new NetworkError()); } } } }
-
combineHeaders的解析
private static List<Header> combineHeaders(List<Header> responseHeaders, Entry entry) { // 第一步创建一个大小写不敏感的TreeSet(也就是忽略大小写),将本次网络请求返回的响应头的Key存入其中 Set<String> headerNamesFromNetworkResponse = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); if (!responseHeaders.isEmpty()) { for (Header header : responseHeaders) { headerNamesFromNetworkResponse.add(header.getName()); } } // 第二步,添加缓存中的Header,只要这些Header在响应头里面没出现过,因为当前响应优先级高 List<Header> combinedHeaders = new ArrayList<>(responseHeaders); if (entry.allResponseHeaders != null) { if (!entry.allResponseHeaders.isEmpty()) { for (Header header : entry.allResponseHeaders) { if (!headerNamesFromNetworkResponse.contains(header.getName())) { // 自然这里相同name的Header可能会有多个,因为一个响应Name对应着一个List的Value // 比如一个响应头可以在一次响应中出现多次 combinedHeaders.add(header); } } } } else { /* 遗留下来的缓存只有entry.responseHeaders,allResponseHeaders是否为null由实现类决定, 所以这里还要再判断responseHeaders,两者区别是responseHeaders里面一个响应头Key只会有一个 值与之对应,而allResponseHeaders会包含所有的响应头,如果实现类实现它的话,DiskBasedCache 就支持 */ if (!entry.responseHeaders.isEmpty()) { for (Map.Entry<String, String> header : entry.responseHeaders.entrySet()) { if (!headerNamesFromNetworkResponse.contains(header.getKey())) { combinedHeaders.add(new Header(header.getKey(), header.getValue())); } } } } return combinedHeaders; }
-
来看看getCacheHeaders是怎么执行的,如果有缓存则请求可能会带上If-None-Match和If-Modified-Since两个请求头
// BasicNetwork.java private Map<String, String> getCacheHeaders(Cache.Entry entry) { if (entry == null) { return Collections.emptyMap(); } Map<String, String> headers = new HashMap<>(); // 会根据过期的响应来带上这两个请求头,为了尽可能的使用缓存,因为如果带上If-None-Match,服务端发现资源 // 没有发生变化那么就会把If-None-Match置为false,并且直接返回304(not modify),客户端就仍然可以使用 // 缓存的数据,资源变了才会返回200,带上响应数据If-None-Match优先级高于If-Modified-Since // 这里讲的挺好的https://www.cnblogs.com/softidea/p/5986339.html if (entry.etag != null) { headers.put("If-None-Match", entry.etag); } // 该请求头标志着资源的最后修改时间,询问服务端从lastModify开始文件有没有发生变化,缺点精度直到秒, // 有时候文件改了但是内容没改 if (entry.lastModified > 0) { headers.put( "If-Modified-Since", HttpHeaderParser.formatEpochAsRfc1123(entry.lastModified)); } return headers; }
-
接着看看 mBaseHttpStack.executeRequest
// AdaptedHttpStack 作用就是再把Apache的HttpResponse转化为Volley的HttpResponse public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { org.apache.http.HttpResponse apacheResp; try { // mHttpStack为HurlStack派生自BaseHttpStack,得到HttpStack返回的apache的HttpResponse apacheResp = mHttpStack.performRequest(request, additionalHeaders); } catch (ConnectTimeoutException e) { // BasicNetwork不知道这个异常应该像timeout一样重试,因为这是一个Apache指定的错误,所以, // 应该包装它抛出一个标准的超时异常 throw new SocketTimeoutException(e.getMessage()); } int statusCode = apacheResp.getStatusLine().getStatusCode(); // 这个数组里面的Header可能有name重复的,因为每个响应头key对应的是一个List org.apache.http.Header[] headers = apacheResp.getAllHeaders(); List<Header> headerList = new ArrayList<>(headers.length); /* 又把Apache的Header转化成了Header 那么前面为什么要多一步去转化成Apache的HttpResponse?我猜 HttpStack接口可能是2.3前使用HttpClient遗留下来的,现在HttpClient已经过时了但是可能是为了 适配HttpClientStack,并没有更改或者移除HttpStack接口,所以需要这个AdapterHttpStack再去把 Apache的HttpResponse转化为Http,不过HttpStack接口的注释说到了不应该去改接口,而应该依赖 BaseHttpStack,因为HttpStack在将来可能会被移除*/ for (org.apache.http.Header header : headers) { headerList.add(new Header(header.getName(), header.getValue())); } // 如果没有响应体再包装成HttpResponse返回 if (apacheResp.getEntity() == null) { return new HttpResponse(statusCode, headerList); } // 如果响应体超出了Int的最大值也就是2G,那么会抛出异常 long contentLength = apacheResp.getEntity().getContentLength(); if ((int) contentLength != contentLength) { throw new IOException("Response too large: " + contentLength); } // 再将Apache的HttpResponse转化为HttpResponse return new HttpResponse( statusCode, headerList, (int) apacheResp.getEntity().getContentLength(), apacheResp.getEntity().getContent()); }
-
接着看mHttpStack.performRequest
// BaseHttpStack.java 这个方法会调用HurlStack请求网络并且将其返回的HttpResponse对象包装成apache的 // HttpResponse对象 public final org.apache.http.HttpResponse performRequest( Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { // 又调用HurlStack.executeRequest,获取到HttpResponse HttpResponse response = executeRequest(request, additionalHeaders); // 维护了protocol、major(主要的)、minor(次要的)相当于HTTP/1.1 ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); // StatusLine是一个接口,相当于维护了响应状态行,HTTP/1.1 200 响应描述由于reasonPhrase为空字符串所以 // 并没有 StatusLine statusLine = new BasicStatusLine( protocolVersion, response.getStatusCode(), /* reasonPhrase= */ ""); // 又被包装成了一个BasicHttpResponse不过里面只有状态栏Header和响应体还没有 BasicHttpResponse apacheResponse = new BasicHttpResponse(statusLine); List<org.apache.http.Header> headers = new ArrayList<>(); for (Header header : response.getHeaders()) { headers.add(new BasicHeader(header.getName(), header.getValue())); } // 设置响应头,现在还缺响应体 apacheResponse.setHeaders(headers.toArray(new org.apache.http.Header[headers.size()])); // 设置响应体,如果存在的话 InputStream responseStream = response.getContent(); if (responseStream != null) { BasicHttpEntity entity = new BasicHttpEntity(); entity.setContent(responseStream); entity.setContentLength(response.getContentLength()); apacheResponse.setEntity(entity); } return apacheResponse; }
-
接着到HurlStack.executeRequest进行真正的网络请求
// HurlStack.java 这里真正做了网络请求会返回一个HttpResponse的javaBean对象 public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { String url = request.getUrl(); HashMap<String, String> map = new HashMap<>(); map.putAll(additionalHeaders); // Request.getHeaders()优先于缓存的header,然而StringResult并没有重写该方法因此返回的空列表 map.putAll(request.getHeaders()); // 默认创建的HurlStack mUrlRewriter为null,创建HurlStack的时候可以传入该对象 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); HttpURLConnection connection = openConnection(parsedUrl, request); boolean keepConnectionOpen = false; try { // 可能会带上If-None-Match 和 If-Modify-Since这两个请求头 for (String headerName : map.keySet()) { connection.setRequestProperty(headerName, map.get(headerName)); } // 该方法设置请求方法,如果是post请求还要带上post参数 setConnectionParametersForRequest(connection, request); // 开始请求会阻塞在这里 int responseCode = connection.getResponseCode(); if (responseCode == -1) { // 如果响应码无法接受,就返回-1,标志着某个错误 throw new IOException("Could not retrieve response code from HttpUrlConnection."); } // 如果没有响应体,那么直接根据responseCode和响应头组装成一个HttpResponse if (!hasResponseBody(request.getMethod(), responseCode)) { // convertHeaders只是把Map转化为List,至于HttpResponse也很简单相当于维护了状态码、响应头 // Content-length、响应体(InputStream) return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields())); } // 需要保存连接打开直到该输入流被调用者消费,比如调用close将断开连接 keepConnectionOpen = true; return new HttpResponse( responseCode, convertHeaders(connection.getHeaderFields()), connection.getContentLength(), new UrlConnectionInputStream(connection)); } finally { // 这里主要是为了避免上面代码抛出了异常而没有关闭流 if (!keepConnectionOpen) { connection.disconnect(); } } } private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException { HttpURLConnection connection = createConnection(url); // 内部从RetryPolicy(重试策略)中获取超时时间如果用户没有手动设置那么就是DefaultRetryPolicy,时间为2500ms int timeoutMs = request.getTimeoutMs(); connection.setConnectTimeout(timeoutMs); connection.setReadTimeout(timeoutMs); // 这里当然要不使用缓存了 connection.setUseCaches(false); // 需要响应流 connection.setDoInput(true); // 如果构造HurlStack有传入mSslSocketFactory,并且url的协议是https,使用SSLSocketFactory // 创建方式见https://blog.csdn.net/c5113620/article/details/80441327 if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) { ((HttpsURLConnection) connection).setSSLSocketFactory(mSslSocketFactory); } return connection; } protected HttpURLConnection createConnection(URL url) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); // 设置本次请求是否自动重定向 connection.setInstanceFollowRedirects(HttpURLConnection.getFollowRedirects()); return connection; } static void setConnectionParametersForRequest( HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError { switch (request.getMethod()) { case Method.DEPRECATED_GET_OR_POST: // 这个方式已经过时,但为了向后兼容仍然使用,建议的方法是getBody() byte[] postBody = request.getPostBody(); // 如果postBody为null那么使用默认的get请求 if (postBody != null) { connection.setRequestMethod("POST"); addBody(connection, request, postBody); } break; case Method.GET: // 没有必要设置请求方法为get因为默认就是get connection.setRequestMethod("GET"); break; // 忽略其他的请求方法 default: throw new IllegalStateException("Unknown method type."); } public byte[] getPostBody() throws AuthFailureError { // 这个方法已经过时了但是要保留(而不是简简单单的通过调用getBody替换),因为这个方法会调用getPostParams // 和getPostParamsEncoding一些过去的代码可能会在发送post请求时重写这两个方法 Map<String, String> postParams = getPostParams(); if (postParams != null && postParams.size() > 0) { return encodeParameters(postParams, getPostParamsEncoding()); } return null; } // 添加请求体 private static void addBody(HttpURLConnection connection, Request<?> request, byte[] body) throws IOException { // 开启输出,不需要明确的设置Content-length,HttpUrlConnection会根据输出流的长度确定 connection.setDoOutput(true); // 设置Content_type,如果在Request.getHeaders()过程中没有设置 if (!connection.getRequestProperties().containsKey(HttpHeaderParser.HEADER_CONTENT_TYPE)) { connection.setRequestProperty( HttpHeaderParser.HEADER_CONTENT_TYPE, request.getBodyContentType()); } DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(body); out.close(); } // 判断当前响应是否有响应体 private static boolean hasResponseBody(int requestMethod, int responseCode) { // 不能是HEAD、响应码不能处于[100,200),并且不等于204、304 return requestMethod != Request.Method.HEAD && !(HTTP_CONTINUE <= responseCode && responseCode < HttpURLConnection.HTTP_OK) && responseCode != HttpURLConnection.HTTP_NO_CONTENT && responseCode != HttpURLConnection.HTTP_NOT_MODIFIED; } // 304状态下的合并请求头,304响应没有包括所有的Header,所以我们不得不使用缓存里面的响应头加上这次返回 // 的响应头 private byte[] inputStreamToBytes(InputStream in, int contentLength) throws IOException, ServerError { // 该类派生自ByteArrayOutputStream,该类内部是用ByteArrayPool用于替代每次申请内存,所以Volley // 善于处理高频率的请求,同时由于其把所有的响应内容全部读取到内存所以不适合大文件的下载 PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(mPool, contentLength); byte[] buffer = null; try { if (in == null) { throw new ServerError(); } buffer = mPool.getBuf(1024); int count; while ((count = in.read(buffer)) != -1) { bytes.write(buffer, 0, count); } return bytes.toByteArray(); } finally { try { // 将PoolingByteArrayOutputStream里面的buf放回ByteArrayPool,下次用的时候可能就能 // 复用这个字节数组 if (in != null) { in.close(); } } catch (IOException e) { VolleyLog.v("Error occurred when closing InputStream"); } // 把当前申请的一个字节的buffer也放入到ByteArrayPool中 mPool.returnBuf(buffer); bytes.close(); } }
-
RetryPolicy的作用,该接口定义了以下三个方法
// 在每次请求时,框架都会调用该方法来个HttpUrlConnection设置超时时间 int getCurrentTimeout(); // 该方法只用于响应较慢超过3秒打印log时,可以不需要实现 int getCurrentRetryCount(); // 当上一次请求失败,而又是可以重试的框架会调用该方法方法内部可以有两个选择一是抛出异常表示不想再重试了就当做失败好了,二是抛出异常继续重试 void retry(VolleyError error) throws VolleyError;
Volley的日志管理
- 源码中好多地方都看到了request.addMarker()来探究一下
public void addMarker(String tag) { if (MarkerLog.ENABLED) { mEventLog.add(tag, Thread.currentThread().getId()); } } // VolleyLog$MarkerLog public static final boolean ENABLED = VolleyLog.DEBUG; // VolleyLog, isLoggable是一个native方法,默认level < info返回false, 这里如果不做任何其他操作那么DEBUG肯定是false,我们可以使用以下adb命令修改默认level // adb shell setprop log.tag.Volley VERBOSE // yourlevel包括VERBOSE、DEBUG等 public static boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE); // Request private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null; // MarkerLog public synchronized void add(String name, long threadId) { if (mFinished) { throw new IllegalStateException("Marker added to finished log"); } // mMarkers就是一个简单的列表 mMarkers.add(new Marker(name, threadId, SystemClock.elapsedRealtime())); } // 到此日志就把添加到mMarkers里面去了,那么时候打印日志呢?答案是调用Request.finish() // Request.java 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()) { // 在主线程中调用 Handler mainThread = new Handler(Looper.getMainLooper()); mainThread.post( new Runnable() { @Override public void run() { mEventLog.add(tag, threadId); mEventLog.finish(Request.this.toString()); } }); return; } mEventLog.add(tag, threadId); mEventLog.finish(this.toString()); } } // VolleyLog$MarkerLog,finish方法内部进行打印 public synchronized void finish(String header) { mFinished = true; long duration = getTotalDuration(); if (duration <= MIN_DURATION_FOR_LOGGING_MS) { return; } long prevTime = mMarkers.get(0).time; d("(%-4d ms) %s", duration, header); for (Marker marker : mMarkers) { long thisTime = marker.time; d("(+%-4d) [%2d] %s", (thisTime - prevTime), marker.thread, marker.name); prevTime = thisTime; } } private long getTotalDuration() { if (mMarkers.size() == 0) { return 0; } // 最后一个减第一个 long first = mMarkers.get(0).time; long last = mMarkers.get(mMarkers.size() - 1).time; return last - first; } // 日志大致如下 2018-12-11 18:41:50.853 15351-15351/com.hefuwei.lwsbooks D/Volley: [1] MarkerLog.finish: (126052 ms) [ ] http://www.baidu.com 0x2f78eb1c NORMAL 1 2018-12-11 18:41:50.853 15351-15351/com.hefuwei.lwsbooks D/Volley: [1] MarkerLog.finish: (+0 ) [ 1] add-to-queue 2018-12-11 18:41:50.853 15351-15351/com.hefuwei.lwsbooks D/Volley: [1] MarkerLog.finish: (+0 ) [6420] network-queue-take 2018-12-11 18:41:50.854 15351-15351/com.hefuwei.lwsbooks D/Volley: [1] MarkerLog.finish: (+2012) [6420] socket-retry [timeout=2000] 2018-12-11 18:41:50.854 15351-15351/com.hefuwei.lwsbooks D/Volley: [1] MarkerLog.finish: (+4005) [6420] socket-retry [timeout=4000] 2018-12-11 18:41:50.854 15351-15351/com.hefuwei.lwsbooks D/Volley: [1] MarkerLog.finish: (+8009) [6420] socket-retry [timeout=8000] 2018-12-11 18:41:50.854 15351-15351/com.hefuwei.lwsbooks D/Volley: [1] MarkerLog.finish: (+16010) [6420] socket-retry [timeout=16000] 2018-12-11 18:41:50.855 15351-15351/com.hefuwei.lwsbooks D/Volley: [1] MarkerLog.finish: (+32005) [6420] socket-retry [timeout=32000] 2018-12-11 18:41:50.855 15351-15351/com.hefuwei.lwsbooks D/Volley: [1] MarkerLog.finish: (+64006) [6420] socket-timeout-giveup [timeout=64000] 2018-12-11 18:41:50.855 15351-15351/com.hefuwei.lwsbooks D/Volley: [1] MarkerLog.finish: (+0 ) [6420] post-error 2018-12-11 18:41:50.855 15351-15351/com.hefuwei.lwsbooks D/Volley: [1] MarkerLog.finish: (+5 ) [ 1] done
总结
- 由于Volley内部使用了ByteArrayPool避免每次都去新建字节数组对象,所以适用于处理高频率的请求,同时由于其将所有响应通过字节数组输出流读入了内存所以其不适合进行大文件的下载,否则容易造成OOM。
- 如果需要发送Post请求可以继承Result,重写getParams方法
- Volley的Request可以取消,一旦取消就不会回调deliverResponse、deliverError,前提是调用cancel的线程需要是主线程(因为ResponseDeliveryRunnable是运行在主线程的),因为如果其他线程在检测Request是否取消发现没有取消则按未取消逻辑走这时其他线程再取消请求那么就会导致取消失效。
- 我们可以实现RetryPolicy来控制每次请求的超时时间,和最大重试次数
- 如果需要给Request加上自定义的请求头可以重写Request.getHeaders