官方文档: https://developer.android.google.cn/training/volley/index.html
项目GitHub地址: https://github.com/google/volley
Volley 是一个可让 Android 应用更轻松、(最重要的是)更快捷地联网的 HTTP 库。
Volley 具有以下优势:
- 自动网络请求调度。
- 多个并发网络连接。
- 透明磁盘和具有标准 HTTP 缓存一致性的内存响应缓存。
- 支持请求优先级。
- 取消请求 API。您可以取消单个请求,也可以设置要取消的请求的时间段或范围。
- 可轻松自定义,例如自定义重试和退避时间。
- 强大的排序功能,让您可以轻松使用从网络异步提取的数据正确填充界面。
- 调试和跟踪工具。
整体流程图
详细流程信息上图已表示的很清楚,建议通过大图详看,下面就几个大块做进一步了解
常见类说明
- RequestManager:通过单例的方式创建RequestQueue,并对外暴露RequestQueue,和提供RequestQueue.add(request)方法。
- Volley:对外暴露newRequestQueue(…) 方法,通过Build.VERSION.SDK_INT >= 9 条件控制创建BasicNetwork对象时传入参数为HurlStack还是HttpClientStack,前者基于HttpURLConnection后者基于HttpClient、这里指定了缓存目录、新建并启动一个请求队列RequestQueue。
- RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、NetworkDispatcher数组(默认有4个,用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口,默认走主线程),通过 start() 函数启动CacheDispatcher和NetworkDispatchers,这里还具体实现了add方法,决定了当前request应该放入到哪个阻塞队列里面。
- BasicNetwork:实现了Network接口具体实现performRequest方法,里面通过死循环不停的执行外部传入的request,并获取response输入流然后构造NetworkResponse返回回去并做部分异常重试机制
- CacheDispatcher:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中获取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。
- NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。
- ResponseDelivery:对response进行分发的接口,具体由ExecutorDelivery实现,它是跟随RequestQueue一起创建,并且handler在主线程。
- Request:表示一个请求的抽象类。StringRequest、JsonRequest、ImageRequest 都是它的子类,表示某种类型的请求。
- HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack。
- Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCache。NetworkDispatcher得到请求结果后判断是否需要存储在 Cache,CacheDispatcher会从 Cache 中取缓存结果。
Connection获取
public URLConnection openConnection() throws java.io.IOException {
return handler.openConnection(this);
}
private static URLStreamHandler createBuiltinHandler(String protocol)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
URLStreamHandler handler = null;
if (protocol.equals("file")) {
handler = new sun.net.www.protocol.file.Handler();
} else if (protocol.equals("ftp")) {
handler = new sun.net.www.protocol.ftp.Handler();
} else if (protocol.equals("jar")) {
handler = new sun.net.www.protocol.jar.Handler();
} else if (protocol.equals("http")) {
handler = (URLStreamHandler)Class.
forName("com.android.okhttp.HttpHandler").newInstance();
} else if (protocol.equals("https")) {//通过反射获取OKhttp的HttpsHandler对象
handler = (URLStreamHandler)Class.
forName("com.android.okhttp.HttpsHandler").newInstance();
}
return handler;
}
通过跟踪和查看源码,最终connection是通过OkHttpClient 获取的,所以这里也就使用到了OKhttp的连接池复用
重试机制
代码大致逻辑如下:
public NetworkResponse performRequest(Request> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
List responseHeaders = Collections.emptyList()
//.......移除部分代码......
try {
httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
int statusCode = httpResponse.getStatusCode();
InputStream inputStream = httpResponse.getContent();
if (inputStream != null) {
responseContents =
NetworkUtility.inputStreamToBytes(
inputStream, httpResponse.getContentLength(), mPool);
} else {
responseContents = new byte[0];
}
return new NetworkResponse(
statusCode,
responseContents,
/* notModified= */ false,
SystemClock.elapsedRealtime() - requestStart,
responseHeaders);
} catch (IOException e) {
RetryInfo retryInfo =
NetworkUtility.shouldRetryException(
request, e, requestStart, httpResponse, responseContents);
NetworkUtility.attemptRetryOnException(request, retryInfo);
}
}
}
static void attemptRetryOnException(final Request> request, final RetryInfo retryInfo)
throws VolleyError {
//.......移除部分代码......
final RetryPolicy retryPolicy = request.getRetryPolicy();
final int oldTimeout = request.getTimeoutMs();
try {
retryPolicy.retry(retryInfo.errorToRetry);
} catch (VolleyError e) {
throw e;
}
}
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++;
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (!hasAttemptRemaining()) {
throw error;
}
}
通过上面的代码逻辑可以得出以下结论:
- performRequest中通过一个死循环来执行网络请求
- 当发生网络异常时会被catch住,并通过attemptRetryOnException发起重试机制
- attemptRetryOnException中通过retry方法控制是否需要重试,如果需要重试则不做任何处理,继续执行死循环,如果不需要重试或者重试发生异常直接抛出exception跳出死循环
- 如果我们需要自定义重试逻辑可以实现RetryPolicy接口,并通过request的setRetryPolicy方法进行配置
RetryPolicy内容如下:
public interface RetryPolicy {
int getCurrentTimeout();
int getCurrentRetryCount();
void retry(VolleyError error) throws VolleyError;
}
volley内部有一个DefaultRetryPolicy类,该类默认实现了RetryPolicy接口,并且我们通过volley创建的每个request默认都会设置这个重试实现,DefaultRetryPolicy的主要逻辑如下:
- 设置默认的超时时间为15000ms、默认的最大重试次数为1次、默认的补偿倍数为1
- 每次重试的超时时间 = 当前超时时间 + 当前超时时间 * 补偿倍数
自定义DNS解析
在HurlStack的executeRequest方法中才是真正做链接创建和IO流的收发
public HttpResponse executeRequest(Request> request, Map additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();//获取request中的请求地址URL
if (mUrlRewriter != null) {//控制是否重写URL,这里我们就可以做自定义dns解析
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);
//.......移除部分代码......
try {
setConnectionParametersForRequest(connection, request);
int responseCode = connection.getResponseCode();
return new HttpResponse(
responseCode,
convertHeaders(connection.getHeaderFields()),
connection.getContentLength(),
createInputStream(request, connection));
} finally {
}
}
public interface UrlRewriter {
@Nullable
String rewriteUrl(String originalUrl);
}
通过上面代码我们可以知道,volley在真正发起网络请求之前会判断UrlRewriter是否为null,如果不为null则会执行它的rewriteUrl方法,我们就可以通过实现UrlRewriter接口并重写rewriteUrl来实现自定义DNS预解析的操作,具体实现可以通过继承HurlStack,在初始化父类HurlStack时传入自己实现的UrlRewriter接口
自定义日志输出
request.addMarker("cache-queue-take"); //添加日志信息通过VolleyLog输出
request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED); //如果设置了RequestEventListener则通过onRequestEvent方法将数据发送出去
public void addMarker(String tag) {
if (MarkerLog.ENABLED) {
mEventLog.add(tag, Thread.currentThread().getId());
}
}
void sendEvent(@RequestQueue.RequestEvent int event) {
if (mRequestQueue != null) {
mRequestQueue.sendRequestEvent(this, event);
}
}
private final List mEventListeners = new ArrayList<>();
void sendRequestEvent(Request> request, @RequestEvent int event) {
synchronized (mEventListeners) {
for (RequestEventListener listener : mEventListeners) {
listener.onRequestEvent(request, event);
}
}
}
public interface RequestEventListener {
void onRequestEvent(Request> request, @RequestEvent int event);
}
在查看volley源码的过程中我们可以看到很多request.addMarker和request.sendEvent,这两个方法就是在记录请求过程的日志信息:
- request.addMarker:是通过volley自带的VolleyLog做日志的记录和输出
- request.sendEvent:是通过RequestEventListener将请求过程发送出去
所以我们可以通过实现RequestEventListener接口并重写onRequestEvent方法来做请求过程日志的持久化,可以通过获取RequestQueue然后通过addRequestEventListener方法将自定义的RequestEventListener传递进去。
总结
- 底层connection的获取还是通过OKhttp来处理的
- 默认会创建4个NetworkDispatcher和1个CacheDispatcher并发执行请求。
- android2.3以下使用HttpClient请求网络接口,以上使用HttpURLConnection。
- 创建了一个大小为4096的ByteArrayPool池,用于IO操作时减少内存消耗。
- NetworkDispatcher和CacheDispatcher通过阻塞队列去执行request请求。
- 通过responseContents =NetworkUtility.inputStreamToBytes(inputStream, httpResponse.getContentLength(), mPool); 将response放入byte[]中,即全部加载进内存。
下面放一张Google官方提供一张线程切换大图: