/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.volley.toolbox; import android.os.SystemClock; import com.android.volley.AuthFailureError; import com.android.volley.Cache; import com.android.volley.Cache.Entry; import com.android.volley.ClientError; import com.android.volley.Network; import com.android.volley.NetworkError; import com.android.volley.NetworkResponse; import com.android.volley.NoConnectionError; import com.android.volley.Request; import com.android.volley.RetryPolicy; import com.android.volley.ServerError; import com.android.volley.TimeoutError; import com.android.volley.VolleyError; import com.android.volley.VolleyLog; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.SocketTimeoutException; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.impl.cookie.DateUtils; /** * A network performing Volley requests over an {@link HttpStack}. */ /* * BasicNetwork 是 目前 Volley 内,Network 接口的唯一实现类 * 在 Volley 内,处理了 网络请求 的表层使用接口 * * performRequest(...) 的要做的事情是: * 1. 调用 HttpStack 接口的实现类 ( HurlStack, HttpClientStack ) 去执行网络请求 * 拿到一个 Apache HttpResponse * 2. 将 Apache HttpResponse -> Volley NetworkResponse 进行转化,并返回 */ public class BasicNetwork implements Network { // 获取 VolleyLog 中的 debug 开关 protected static final boolean DEBUG = VolleyLog.DEBUG; // 请求 debug log 的最小间隔 private static int SLOW_REQUEST_THRESHOLD_MS = 3000; // ByteArrayPool 池的默认 byte[] 总长度之和( 容量 ) private static int DEFAULT_POOL_SIZE = 4096; // HttpStack 接口 ( HurlStack, HttpClientStack ) protected final HttpStack mHttpStack; /* * ByteArrayPool * 用于 byte[] 的回收再利用,减少内存分配和回收。 */ protected final ByteArrayPool mPool; /** * @param httpStack HTTP stack to be used */ /* * 默认的 ByteArrayPool:是一个 容量为 4096 的 ByteArrayPool */ public BasicNetwork(HttpStack httpStack) { // If a pool isn't passed in, then build a small default pool that will give us a lot of // benefit and not use too much memory. this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE)); } /** * @param httpStack HTTP stack to be used * @param pool a buffer pool that improves GC performance in copy operations */ public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) { mHttpStack = httpStack; mPool = pool; } /** * Attempts to prepare the request for a retry. If there are no more attempts remaining in the * request's retry policy, a timeout exception is thrown. * * @param request The request to use. */ /* * 尝试 重试策略 处理 */ private static void attemptRetryOnException(String logPrefix, Request<?> request, VolleyError exception) throws VolleyError { // 拿到 该请求 Request 内对应的 重试策略 类 RetryPolicy retryPolicy = request.getRetryPolicy(); // 获取请求此次的超时时间 int oldTimeout = request.getTimeoutMs(); try { /* * 进行 重试: * 根据 请求此次的超时时间 * 退避乘数 = 此次的实际超时时间 * 将 实际超时时间 累加到 重试策略类 中的 超时总时长 内 */ retryPolicy.retry(exception); } catch (VolleyError e) { // 打印 log request.addMarker( String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout)); throw e; } // 打印 log request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout)); } /** * Converts Headers array to Map. */ /* * 进行 Apache Header[] -> Map<String, String> 转换 */ protected static Map<String, String> convertHeaders(Header[] headers) { Map<String, String> result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); for (int i = 0; i < headers.length; i++) { result.put(headers[i].getName(), headers[i].getValue()); } return result; } /* * 执行处理 Volley内的 抽象请求 Request<?> * 但是 HttpStack 处理后,都返回 Apache 的请求结果( HttpResponse ) * performRequest(...) 接下来会将:Apache HttpResponse -> Volley NetworkResponse 进行转化 */ @Override public NetworkResponse performRequest(Request<?> request) throws VolleyError { // 记录下 请求开始时间 long requestStart = SystemClock.elapsedRealtime(); // 进入一个 循环体 while (true) { // 用于保存 请求结果( 响应 ) HttpResponse httpResponse = null; // 用于保存 请求结果( 响应 )的 body byte[] responseContents = null; // 用于保存 请求结果( 响应 )的 Header Map<String, String> responseHeaders = Collections.emptyMap(); try { // Gather headers. Map<String, String> headers = new HashMap<String, String>(); /* * 拿出缓存 Response Header 的数据 * 放到 headers 内 * 用于此次请求 */ addCacheHeaders(headers, request.getCacheEntry()); // 调用 HttpStack 执行请求,拿到 请求结果( 响应 ) httpResponse = mHttpStack.performRequest(request, headers); // 提取 状态行 信息 StatusLine statusLine = httpResponse.getStatusLine(); // 拿到 请求结果( 响应 )的状态码 int statusCode = statusLine.getStatusCode(); // 进行 Apache Header[] -> Map<String, String> 转换 responseHeaders = convertHeaders(httpResponse.getAllHeaders()); // Handle cache validation. /* * 状态码 304: Not Modified */ if (statusCode == HttpStatus.SC_NOT_MODIFIED) { // 获取 该请求的缓存 Entry Entry entry = request.getCacheEntry(); /* * 没有返回 缓存 Entry * * data 返回 null * header 采用此次请求的 responseHeaders * * 封装成一个 NetworkResponse */ if (entry == null) { 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 // 将此次请求 的 responseHeaders 放入 缓存 Entry 的 header 内 entry.responseHeaders.putAll(responseHeaders); /* * data 返回 缓存内的数据 data * header 为 缓存内的 header * * 封装成一个 NetworkResponse */ 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. /* * 这里处理一些 像 状态码 204: No Content * 判断 Apache HttpResponse 中 的 HttpEntity 是否存在 */ if (httpResponse.getEntity() != null) { /* * 如果 Apache HttpResponse 中 的 HttpEntity 存在 * 那么 执行 Apache HttpEntity -> byte[] 的转化 */ responseContents = entityToBytes(httpResponse.getEntity()); } else { // Add 0 byte response as a way of honestly representing a // no-content request. /* * 如果 Apache HttpResponse 中 的 HttpEntity 存在 * 为了不让 responseContents 不为 null,只能创建一个 byte[0] */ responseContents = new byte[0]; } // if the request is slow, log it. /* * 拿到 当前的时间,和刚才请求执行前记录的时间 * 去计算 这个请求的运行时间 = 当前的时间 - 请求执行前记录的时间 */ long requestLifetime = SystemClock.elapsedRealtime() - requestStart; // 判断 请求时长是否在 3000ms 以上。是的话,打印 log logSlowRequests(requestLifetime, request, responseContents, statusLine); /* * 200-299 用于表示请求成功。 */ if (statusCode < 200 || statusCode > 299) { throw new IOException(); } /* * statusCode:状态码 * responseContents:请求结果( 响应 ) content * responseHeaders:请求结果( 响应 ) header * notModified:false * SystemClock.elapsedRealtime() - requestStart: 请求时长 * * 封装成一个 NetworkResponse,并且返回 */ return new NetworkResponse(statusCode, responseContents, responseHeaders, false, SystemClock.elapsedRealtime() - requestStart); } catch (SocketTimeoutException e) { // 尝试 重试,这里只是累加 实际超时时间而已 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) { int statusCode; // 如果 没有 请求结果( 响应 ),判断为 网络错误 if (httpResponse != null) { statusCode = httpResponse.getStatusLine().getStatusCode(); } else { throw new NoConnectionError(e); } VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); NetworkResponse networkResponse; // 状态码 204: No Content 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 if (statusCode >= 400 && statusCode <= 499) { // Don't retry other client errors. 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()); } } } } /** * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete. */ /* * 如果 * 1. Debug log 的开关已经打开 * 2. 请求时长超过 SLOW_REQUEST_THRESHOLD_MS = 3000ms * * 打印请求结果( 响应 )相关信息:请求时长、Volley 抽象请求 Request、 * 请求结果( 响应 ) content,请求结果( 响应 )状态行 */ private void logSlowRequests(long requestLifetime, Request<?> request, byte[] responseContents, StatusLine statusLine) { if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) { VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " + "[rc=%d], [retryCount=%s]", request, requestLifetime, responseContents != null ? responseContents.length : "null", statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount()); } } /* * 将 Request 内 Entry 保存的 * Response Header 数据 抽出来 * 放到 headers Map 里返回,用于此次请求 */ private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) { // If there's no cache entry, we're done. if (entry == null) { return; } if (entry.etag != null) { // 设置 If-None-Match headers.put("If-None-Match", entry.etag); } if (entry.lastModified > 0) { Date refTime = new Date(entry.lastModified); // 设置 If-Modified-Since headers.put("If-Modified-Since", DateUtils.formatDate(refTime)); } } /* * 打印 错误 */ protected void logError(String what, String url, long start) { long now = SystemClock.elapsedRealtime(); VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url); } /** Reads the contents of HttpEntity into a byte[]. */ /* * 执行 Apache HttpEntity -> byte[] 的转化 */ private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError { /* * 实例化一个 PoolingByteArrayOutputStream 对象 * * PoolingByteArrayOutputStream 内设置了一个 ByteArrayPool,会将一些 实例化好的 byte[] 进行缓存 * 然后回收利用,提供读取流数据 * * 将当前 BasicNetwork 的 ByteArrayPool 提供 给 PoolingByteArrayOutputStream * 还需要将 传入一个 需要 output 的数据长度 给 PoolingByteArrayOutputStream,这里用 * (int) entity.getContentLength() 进行获取 */ PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength()); byte[] buffer = null; try { // 获取 Apache HttpEntity 的 content 数据流 InputStream in = entity.getContent(); if (in == null) { throw new ServerError(); } // 获取一个 byte[].length = 1024 的 byte[] buffer = mPool.getBuf(1024); int count; // 开始 边读边写 while ((count = in.read(buffer)) != -1) { // 写入到 PoolingByteArrayOutputStream 中 bytes.write(buffer, 0, count); } // 从 PoolingByteArrayOutputStream 中取出 byte[] 数据 return bytes.toByteArray(); } finally { try { // Close the InputStream and release the resources by "consuming the content". // 调用 Apache API 关闭 Apache HttpEntity 的 content 数据流 entity.consumeContent(); } catch (IOException e) { // This can happen if there was an exception above that left the entity in // an invalid state. VolleyLog.v("Error occured when calling consumingContent"); } // 将使用的 byte[] 放入 ByteArrayPool 缓存回收 mPool.returnBuf(buffer); // 关闭 PoolingByteArrayOutputStream 流 bytes.close(); } } }