从源码角度分析Volley的请求流程
这两天断断续续的看了下Volley的源码。正所谓,纸上得来终觉浅 绝知此事要躬行。我得写个博客讲述下。巩固下,加深印象。所以,今天,咱们来分析一波Volley的整个请求流程。
简单的说下,平时我们使用Volley,就是创建各种Request,什么StringRequest或者JsonObjectRequest。把它们add进去Volley的RequestQueue里面,然后调用个RequestQueue.start方法即可开始网络请求,最后从传进去的回调里面拿到网络请求回来的数据。这就是咱们平时的一个使用volley的操作流程。
那今天,咱们就要了解下这个流程的具体实现。
CacheDispatcher
众所周知,调用RequestQueue的start方法后,就可以开始执行网络请求了。那这里面肯定就是个入口。咱们就直接看start方法的源码即可
RequestQueue.Java -141行
/** * Starts the dispatchers in this queue. */ public void start() { stop(); // Make sure any currently running dispatchers are stopped. // Create the cache dispatcher and start it. mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); mCacheDispatcher.start(); // Create network dispatchers (and corresponding threads) up to the pool size. for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } }
可以看到,里面先调用了个stop方法,然后接着new了个CacheDispatcher,传了一堆参数进去。然后把它start起来了,第一眼看到,start?有点像线程。的确,CacheDispatcher就是继承于线程,在run方法里面做了缓存请求的调度工作,就是走缓存不走网络,直接缓存里面拿数据。以下是传入的几个参数对像:
RequestQueue.Java
/** The cache triage queue. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>(); /** The queue of requests that are actually going out to the network. */ private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>(); /** Cache interface for retrieving and storing responses. */ private final Cache mCache; /** Response delivery mechanism. */ private final ResponseDelivery mDelivery;
mCacheQueue:存放走缓存请求的请求队列;
mNetworkQueue:存放走网络请求的请求队列;
mCache:实现缓存的类。Cache是个接口,这里Volley实现Cache接口的是DiskBasedCache类。把缓存数据存在硬盘里面。
提供get方法和put方法读取缓存数据。
mDelivery:用于分发请求后拿到的数据。ResponseDelivery也是个接口,具体实现类是ExecutorDelivery。提供postResponse和postError用于分发成功和错误的回调。
这时候,咱们看看CacheDispatcher的run方法,毕竟它是个线程,主要实现就是看run方法里面代码了。
@Override public void run() { if (DEBUG) VolleyLog.v("start new dispatcher"); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 初始化缓存,就是把磁盘的缓存数据加载到内存中去 mCache.initialize(); while (true) { try { //从队列 final Request<?> request = mCacheQueue.take(); request.addMarker("cache-queue-take"); // 判断请求是否取消了,取消就不执行了。所以,如果在请求已经开始后,再去cancel那就没啥卵用了 if (request.isCanceled()) { request.finish("cache-discard-canceled"); continue; } // 通过这个请求的CacheKey从缓存里面找找有没有这个请求的缓存,getCacheKey的值默认是url Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) { request.addMarker("cache-miss"); // 如果没有缓存,那就把请求丢到网络请求队列mNetworkQueue里面去,让它直接请求网络加载数据 mNetworkQueue.put(request); continue; } // 如果缓存过期,则直接请求网络加载数据。这里的判断过期是用的TTL,意思是缓存的过期时间 if (entry.isExpired()) { request.addMarker("cache-hit-expired"); request.setCacheEntry(entry); mNetworkQueue.put(request); continue; } // 解析缓存的数据为response对象 request.addMarker("cache-hit"); Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); if (!entry.refreshNeeded()) { // 判断是否需要刷新数据,false则直接把上面解析的缓存数据response分发下去,最终回调到success和error的监听里去 mDelivery.postResponse(request, response); } else { // 一些基本操作 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) { // 用于退出和结束线程 if (mQuit) { return; } continue; } } }
上面的注释也写的很明白了。CacheDispatcher做的事情也很简单。咱们再来描述一次,首先,从缓存请求队列里面拿到请求,先判断这个请求是否给取消了,如果给取消了,那就continue,继续拿去队列里面拿下一个请求。然后,通过请求的CacheKey去拿缓存里面的数据,CacheKey的值默认为url。拿到缓存的数据后,先判断下这个缓存的数据是否为null,如果为null,那就代表没有缓存,那就把这个请求丢到网络请求队列mNetworkQueue里面去轮询执行网络请求。如果缓存数据不为null,那就判断有没有过期,如果过期了,也一样,丢到mNetworkQueue里面去。最后,把这个缓存数据解析成Response对象,然后判断这个请求是否需要刷新数据了,如果不需要,那就直接把这个缓存的请求结果分发出去,最后也是跑到咱们请求的success和error回调里面去。如果需要刷新,还是老规矩,丢到mNetworkQueue里面去。
这样一来,这个CacheDispatcher的工作流程咱们就分析完了。接下来咱们看看另一个调度器。
NetworkDispatcher
顾名思义,这玩意就是网络调度器。跟上面的缓存调度器。差不多,也是个线程。
RequestQueue.java 148行
for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); }
在RequestQueue的start方法中,这NetworkDispatcher 有着多个,具体多少个,看mDispatchers数组的长度,而这个mDispatchers就是个全是NetworkDispatcher 对象的线程池。默认个数是4个,这个可以通过RequestQueue构造方法来进行修改。代码如下:
/** Number of network request dispatcher threads to start. */ private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
public RequestQueue(Cache cache, Network network) { this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); }public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize,new ExecutorDelivery(new Handler(Looper.getMainLooper()))); }
public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; }
也就是说,最多同时有4个请求可以同时请求,而多出来的,就要在队列里面排队了。既然NetworkDispatcher 也是个线程,那咱们就来看看run方法即可
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// 从请求队列里面拿出请求
request = mQueue.take();
} catch (InterruptedException e) {
// 用于结束线程
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// 跟CacheDispatcher一样,一开始也是判断请求是否取消了
// 如果是,那就不执行后面的代码,直接continue继续处理下一个请求
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// 直接通过mNetWork的performRequest去执行网络请求。
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 判断返回的数据是否有变化和判断这个响应的数据是否分发过
// 如果两者都是true,那就不分发了,因为数据没有改变,还是跟上次的一样,而且也分发过了。那就没有再次分发的必要了
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// 通过request的parseNetworkResponse方法来处理返回的数据,把它变成对应的格式,比如String,JsonObject
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// 判断是否需要缓存,如果需要,那就通过mCache写入缓存。shouldCache的返回值默认是true
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// 把request标记为已经分发过数据。并且把response通过mDelivery分发下去
request.markDelivered();
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {//分发error
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {//分发error
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
(可能你会问,为啥这里的代码是白的,上面的都是黑背景的。主要是这个csdn的文章编辑器的锅。。怪他怪他。我排版了半天,都搞不好,最后只能这样了。大家将就一下。)上面就是NetworkDispatcher 处理请求的整个流程。代码比较清晰。还是总结一下流程。
首先队列里面拿到具体的某个请求,判断这个请求是否取消了,如果是,那就continue继续下一个请求。如果不是,那就调用mNetwork的performRequest方法去执行网络请求。mNetwork是Network接口,具体实现是BasicNetwork这个类。BasicNetwork主要根据返回的响应码做一些处理,还有处理超时的请求重试和提取响应头的信息。而具体的网络请求,是通过BasicNetwork类里面的mHttpStack来执行的。HttpStack也是个接口,具体的实现类有两个,HurlStack和HttpClientStack。前者是用HttpUrlConnection去执行具体的网络请求,而后者,看名字就知道是用HttpClient来执行的网络请求。
Volley.java-42行
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) { } if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { // Prior to Gingerbread, HttpUrlConnection was unreliable. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } Network network = new BasicNetwork(stack); RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); queue.start(); return queue; }
在Volley的newRequestQueue方法里面可以看到。在sdk版本号为大于等于9时,就用HttpUrlConnection,而9以下的,就换成HttpClient来实现。因为HttpUrlConnection在2.2版本(sdk版本号为8)及以下安卓系统版本有个bug,调用close()函数会影响连接池,导致连接复用失效,使用HttpURLConnection需要关闭keepAlive。
不过现在都什么年代了。都很少存在4.4以下版本的手机了。基本都是5.0以上,这些就不用管了。
这一样一来,整个请求的发起,队列轮询请求,到最后执行请求并返回用于分发的Response对象。整个流程也就差不多了。还剩个分发的过程,咱们就把这整个请求流程至响应的流程分析完了。现在来看看分发是怎么分发的。
还记得我们看源码里面,分发都是一句代码
mDelivery.postResponse(request, response);
mDelivery.postError(request, volleyError);也就是上面这两句。成功的分发和出错的分发。都是由mDelivery对象来处理。
/** For posting responses and errors. */ private final ResponseDelivery mDelivery;
而ResponseDelivery也是个接口,具体的实现类是ExecutorDelivery。看两眼ExecutorDelivery的代码
ExecutorDelivery
/** * Delivers responses and errors. */ public class ExecutorDelivery implements ResponseDelivery { /** Used for posting responses, typically to the main thread. */ private final Executor mResponsePoster; /** * Creates a new response delivery interface. * @param handler {@link Handler} to post responses on */ 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); } }; } /** * Creates a new response delivery interface, mockable version * for testing. * @param executor For running delivery tasks */ public ExecutorDelivery(Executor executor) { mResponsePoster = executor; } @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)); } /** * A Runnable used for delivering network responses to a listener on the * main thread. */ @SuppressWarnings("rawtypes") private class ResponseDeliveryRunnable implements Runnable { private final Request mRequest; private final Response mResponse; private final Runnable mRunnable; public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { mRequest = request; mResponse = response; mRunnable = runnable; } @SuppressWarnings("unchecked") @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(); } } } }
里面的postResponse和postError方法最后都会调用mResponsePoster对象的execute方法去执行一个ResponseDeliveryRunnable 。ResponseDeliveryRunnable 里就是直接调用了request的deliverResponse和deliverError去分发success和error。而deliverResponse和deliverError是request接口里面需要实现的一个抽象方法。我们随便看一下JsonRequest的实现。
JsonRequest.java-64行
@Override protected void deliverResponse(T response) { mListener.onResponse(response); }
而这里的mListener是什么呢,就是那个我们new一个request请求是传进去的成功回调。所以,最后是调用了我们传进去的成功监听事件,把具体的数据传回给我们。而error的情况也是一致的。
上面还有个东西漏掉了,就是mResponsePoster对象是个啥玩意。
ExecutorDelivery.java
private final Executor mResponsePoster;
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); } }; }
RequestQueue.java-123行
public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); }
可以看到,这里就是简单的实现了一下Executor接口,实际上就是把传进来的runnable丢到主线程去处理。所以,volley的成功和出错回调都在主线程。
最后来一张图简洁的描述下
至此。整个volley的请求流程就完了。有什么问题或者哪里写错字了下面评论告诉我。谢谢大家,谢谢党和组织。山水有相逢,咱们来日再见。
上一篇: 《剑指offer》把数组排成最小的数