Volley源码解析
抽时间看了下Volley的源码,感觉这个框架还是很不错的,这里对其源码进行分析。
主要从以下几个方面分析:
-
简要介绍 Volley 的使用
-
Volley 访问网络的整体流程
-
对源码中一些重要的类的总结
-
一些总结
简要介绍 Volley 的使用
这个不是文章的重点,所以就简单介绍一下。
// 创建请求队列
RequestQueue mQueue = Volley.newRequestQueue(context); // 创建请求
StringRequest stringRequest = new StringRequest("http://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
// 添加请求到队列中
mQueue.add(stringRequest);
更具体的使用可以参考博客 Volley使用
Volley 访问网络的整体流程
这里从使用方法开始进行分析。
首先通过 Volley#newRequestQueue() 创建请求队列。
在Volley类中,一共就提供了两个静态的重载方法 newRequestQueue()
主要看下 newRequestQueue(Context, HttpStack)
// Volley#newRequestQueue()
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0"; try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
} // 创建 HttpStack
if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { // 根据sdk版本选择HttpUrlConnection或者HttpClient
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack); // 封装网络请求的一些操作
// 创建请求队列并启动
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start(); return queue;
}
可以看到,其中主要创建了Network,HttpStack,RequestQueue,并启动了RequestQueue
最终网络访问就封装在HttpStack中,这个后面再看
先跟进到 RequestQueue#start()中看下
// RequestQueue#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#add()代码
这里挑一些主要的代码看
// RequestQueue#add()
// RequestQueue中维护了两个队列,一个是缓存队列,一个是网络请求队列,之后派发器会从这两个队列中获取请求
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>(); private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>(); public <T> Request<T> add(Request<T> request) { // 省略 ...
// 如果请求不需要缓存的话,直接交给网络请求队列去执行,默认是需要缓存的
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;
}
}
从上面代码中可以看到,请求需要缓存的话,会加入到缓存队列中,交给缓存派发器处理,否则交给网络请求派发器处理
接下来看下缓存派发器对请求的处理
由于 CacheDispatcher 和 NetworkDispatcher 都是继承自 Thread , 所以主要看其 run() 函数
// CacheDispatcher#run()
public void run() { // 对缓存池进行初始化
mCache.initialize(); while (true) { // 死循环不断处理请求
try { // 从缓存队列中获取请求,这个缓存队列就是 RequestQueue 中看到的缓存队列
// 如果队列为空,会阻塞在这个地方
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take"); // 请求取消什么都不做
if (request.isCanceled()) {
request.finish("cache-discard-canceled"); continue;
} // 从缓存池中获取请求缓存的内容
Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) {
request.addMarker("cache-miss"); // 缓存池中没有缓存此请求,也加入到网络请求队列中去
mNetworkQueue.put(request); continue;
} // 请求过期,也加入网络请求队列
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request); continue;
}
request.addMarker("cache-hit"); // 根据缓存中获取的内容构建请求的回复
Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed"); if (!entry.refreshNeeded()) { // 缓存不需要刷新,直接交给 ResponseDelivery 去处理,ResponseDelivery后面再看
mDelivery.postResponse(request, response);
} else { // Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry); // Mark the response as intermediate.
response.intermediate = true;
mDelivery.postResponse(request, response, new Runnable() { @Override
public void run() { try { // 如果缓存需要刷新,这个地方还需要再重新请求网络
mNetworkQueue.put(request);
} catch (InterruptedException e) { // Not much we can do about this.
}
}
});
}
} catch (InterruptedException e) { // We may have been interrupted because it was time to quit.
if (mQuit) { return;
} continue;
}
}
}
从上面可以看到,代码不多,也很好理解,如果有缓存,就取出来去处理,没有或者过期,就加入到网络请求队列中去请求,需要网络请求的有几个地方:1. 请求不需要缓存 2. 请求在缓存中没有找到 3. 请求的缓存过期 4. 请求的缓存需要刷新
接下来就看下网络请求派发器
NetworkDispatcher 也是继承 Thread,所以也直接看 run() 函数
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { // 一样是死循环不断处理请求
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request; try { // 这个地方会阻塞,这个队列就是 RequestQueue 中的网络队列
request = mQueue.take();
} catch (InterruptedException e) { // We may have been interrupted because it was time to quit.
if (mQuit) { return;
} continue;
} try {
request.addMarker("network-queue-take"); // 请求取消就什么都不做
if (request.isCanceled()) {
request.finish("network-discard-cancelled"); continue;
}
addTrafficStatsTag(request); // 执行网络请求 其中真正执行的是 httpStack.performRequest
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete"); // If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified"); continue;
} // 对返回的内容进行处理,parseNetworkResponse 需要我们重写,不过框架提供了一些常用的比如 StringRequest 和 JsonRequest
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");
}
request.markDelivered(); // 交给 ResponseDelivery 进一步处理
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} 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);
}
}
}
可以看到,在 NetworkDispatcher 中,最主要的就是调用了 mNetwork.performRequest(request) 执行网络请求,而在 Network 是个接口,具体实现类是 BasicNetwork,在BasicNetwork#performRequest()中调用 httpResponse = mHttpStack.performRequest(request, headers),这里的 mHttpStack 就是前面看到的 HurlStack 和 HttpClientStack。而在 HttpStack 的 performRequest() 中就是具体的网络请求,看下 HurlStack 的。
// 这块就比较熟悉了,就是之前经常用的网络请求
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
HashMap<String, String> map = new HashMap<String, String>();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders); if (mUrlRewriter != null) {
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); for (String headerName : map.keySet()) {
connection.addRequestProperty(headerName, map.get(headerName));
}
setConnectionParametersForRequest(connection, request); // Initialize HttpResponse with data from the HttpURLConnection.
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); 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.");
}
StatusLine responseStatus = new BasicStatusLine(protocolVersion,
connection.getResponseCode(), connection.getResponseMessage());
BasicHttpResponse response = new BasicHttpResponse(responseStatus); if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
response.setEntity(entityFromConnection(connection));
} 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;
}
在上面我们看到,最终会调用 ResponseDelivery#postResponse()
ExecutorDelivery 是 ResponseDelivery 的实现类,可以看下 ExecutorDelivery#postResponse()
// ExecutorDelivery#postResponse()
private final Executor mResponsePoster; public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response"); // 执行任务
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
// ResponseDeliveryRunnable#run()
public void run() { // If this request has canceled, finish it and don't deliver.
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery"); return;
} if (mResponse.isSuccess()) { // 这个就是 request 中自己实现的,在 StringRequest 和 JsonRequest 中都是直接调用了 listener.onResponse()
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
} if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
} // 如果有需要执行的任务,会执行,前面缓存需要刷新的话,就会在这个地方进行网络调用
if (mRunnable != null) {
mRunnable.run();
}
}
以上就是请求的主要流程了,下面附上一张流程图
上面分析中没有提到 Volley 中的重试机制,其实在 BasicNetwork#performRequest() 中实现了失败重试机制。
public NetworkResponse performRequest(Request<?> request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime(); // 这个循环保证了失败后可以重新请求
while (true) { // 省略代码 ...
try { // 执行网络请求
httpResponse = mHttpStack.performRequest(request, headers); // 省略代码 ...
} catch (SocketTimeoutException e) { // 这里通过捕获异常来重试,这里没有捕捉 VolleyError 的异常,所以抛出 VolleyError 异常就会结束循环,不再重试了
attemptRetryOnException("socket", request, new TimeoutError());
} // 省略代码 ...
}
} private static void attemptRetryOnException(String logPrefix, Request<?> request,
VolleyError exception) throws VolleyError {
RetryPolicy retryPolicy = request.getRetryPolicy(); int oldTimeout = request.getTimeoutMs(); try {
retryPolicy.retry(exception); // 重试策略,如果不需要重试,就会抛出 VolleyError 的异常
} catch (VolleyError e) {
request.addMarker(
String.format("%s-timeout-giveup \[timeout=%s\]", logPrefix, oldTimeout)); throw e;
}
request.addMarker(String.format("%s-retry \[timeout=%s\]", logPrefix, oldTimeout));
}
对源码中一些重要的类的总结
其实在上面讲流程的时候已经分析了主要的类了,这里主要做个总结。
Volley中主要的类有这些:
-
Volley: 提供了构建 RequestQueue 的统一方法,我们也可以不通过这个而是自己构建 RequestQueue
-
RequestQueue: 负责分发请求到不同的请求队列中
-
CacheDispatcher: 处理缓存请求
-
NetworkDispatcher: 处理网络请求
-
ResponseDelivery: 获取请求后进行处理
-
Cache: 缓存接口,具体实现类有 DiskBaseCache
-
Network: 网络接口,具体实现类有 BasicNetwork
-
HttpStack: 真正执行请求,具体实现类有 HurlStack HttpClientStack
-
Request: 封装请求信息并处理回复,具体实现类有 StringRequest JsonRequest
-
Response: 封装返回的信息,具体实现类有 NetworkResponse
一些总结
最后再分析一下为什么在 Volley 的 listener 的中可以直接更新UI。
在前面 Volley#newRequestQueue() 中有调用到如下代码
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
跟进会发现最后调用的是
public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
我们上面也提到,最后的 Response 是通过 ResponseDelivery#postResponse() 来分发,而 ExecutorDelivery 是 ResponseDelivery 的实现类,而这个构造函数的实现如下:
public ExecutorDelivery(final Handler handler) { // Make an Executor that just wraps the handler.
mResponsePoster = new Executor() { @Override
public void execute(Runnable command) { handler.post(command); // 请求最终都会调用 handler.post(),而这个 handler 是上面通过 new Handler(Lopper.getMainLopper()) 得到的
}
};
}
请求最终都会调用 handler.post(),而这个 handler 是上面通过 new Handler(Lopper.getMainLopper()) 得到的,所以请求的处理就通过这里分发到主线程中了,也就自然可以更新 UI 了。
最后:
通过对 Volley 源码的分析,可以发现, Volley 框架的拆装性很强,框架默认使用的是 HttpUrlConnection 和 HttpClient 来实现网络请求,如果我们想换成其他,比如okhttp,只要继承 HttpStack 并重写 performRequest() 来处理请求就可以了。对于请求,框架默认提供了 StringRequest 和 JsonRequest,一般情况下这两个是足够用了,但是特别情况下,我们只要继承 Request 并重写 deliverResponse() 和 parseNetworkResponse() 就可以了。
以前自己也有实现过网络框架,相比之下,还是有很多不足,通过阅读源码,还是学到了很多。