内容简介:Volley是Google在2013年推出来的HTTP库,旨在帮助开发者更快更简便的实现网络请求。说说为什么要分析Volley的源码吧,因为Volley中线程的转换时通过我们先整体看下 Volley 是如何进行一个完整的网络请求的:上面代码主要做了三件事:
Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster.
Volley是Google在2013年推出来的HTTP库,旨在帮助开发者更快更简便的实现网络请求。说说为什么要分析Volley的源码吧,因为Volley中线程的转换时通过 Thread
和 Handler
来实现的,跟之前的两篇都有着很大的联系(ps:Okhttp和Retrofit都撕不动^_^),哈哈,后面会一步一步的给大家带来Okhttp和Retrofit等更多的源码分析!
执行一个网络请求
我们先整体看下 Volley 是如何进行一个完整的网络请求的:
val requestQueue: RequestQueue = Volley.newRequestQueue(context) val url = "https://www.baidu.com" val request = StringRequest(url, Response.Listener<String> { Log.d("taonce", "request result is: $it") }, Response.ErrorListener { }) requestQueue.add(request) 复制代码
上面代码主要做了三件事:
- 创建一个请求队列
RequestQueue
:Volley.newRequestQueue(context)
- 创建一个请求
Request
:StringRequest(String url, Listener<String> listener, @Nullable ErrorListener errorListener)
- 将
Request
加入到RequestQueue
:requestQueue.add(request)
接下来通过源码的方法来看看这三步内部做了什么操作。
创建 RequestQueue 和 Request进入 Volley.newRequestQueue(context)
源码:
进入 Volley.newRequestQueue(context)
源码:
public static RequestQueue newRequestQueue(Context context) { // 实际上是调用了另外一个构造方法 return newRequestQueue(context, (BaseHttpStack) null); } 复制代码
继续查看 newRequestQueue(Context context, BaseHttpStack stack)
:
BasicNetwork network; if (stack == null) { // 判断是否大于等于 Android 2.3 版本 if (Build.VERSION.SDK_INT >= 9) { // 如果是 Android 2.3 及其以上,就用 HurlStack() 进行网络请求 network = new BasicNetwork(new HurlStack()); } else { // 如果是 Android 2.3 以下,那么就采用 HttpClientStack(HttpClient) 进行网络请求 network = new BasicNetwork( new HttpClientStack(AndroidHttpClient.newInstance(userAgent))); } } else { // 如果stack不为空,那么就采用传进来的stack network = new BasicNetwork(stack); } return newRequestQueue(context, network); 复制代码
可以得出:
- 创建一个
BasicNetwork
对象 -
stack
为空 ----> Android 2.3 及其以上创建HurlStack()
对象,并且传给network
,HurlStack()
采用的是HttpURLConnetion
进行网络请求的。 -
stack
为空 ----> Android 2.3 以下创建HttpClientStack()
对象,并且传给network
,HttpClientStack()
采用的则是HttpClient
进行网络请求,不过现在( 当前版本1.1.1 )new HttpClientStack(HttpClient client)
已经被标记了@Deprecated
了,因为它采用的HttpClient
, Google 在 Android 6.0 中移除了对 Apache HTTP 客户端的支持,并且从 Android P 开始,org.apache.legacy
库将从bootclasspath
中删除。 -
stack
不为空 ----> 直接将 stack 传给network
- 创建一个
RequestQueue()
对象并返回
到此为止,大家只要记住上面几个对象就好,接下来会慢慢的讲解对象分别做了什么工作。
这里还是要着重介绍下 newRequestQueue()
方法:
private static RequestQueue newRequestQueue(Context context, Network network) { File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); // 创建请求队列 RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); queue.start(); return queue; } 复制代码
创建 RequestQueue()
的过程中有一个很重要的点,我们来看看它的构造方法:
public RequestQueue(Cache cache, Network network) { // 默认 Network Thread 数目为 DEFAULT_NETWORK_THREAD_POOL_SIZE = 4 this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); } public RequestQueue(Cache cache, Network network, int threadPoolSize) { this( cache, network, threadPoolSize, // 注意点就是这:创建 Handler 的时候,传递的是 Looper.getMainLooper(),也就是后面将请求结果回调到主线程的关键。 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; } 复制代码
上面第二个构造方法中创建了一个 ExecutorDelivery()
, 这个对象实现了 ResponseDelivery
接口的类,用来将网络请求的结果或者缓存中的结果分发到主线程。
再来看看上面 queue.start()
方法做了什么操作:
// 开启5个线程 public void start() { // 如果5个线程不为空,先停止它们 stop(); // 创建 CacheDispatcher,并开启它 mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); mCacheDispatcher.start(); // 创建4个NetworkDispatcher,并开启它们 for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } } 复制代码
到此,我们前奏分析完了,有一些概念先不急着看,只要知道它的作用就行,后面我们来一个一个击破。
添加请求到请求队列中 : RequestQueue.add( request )
废话不多了,直接进入源码: RequestQueue.add(request)
public <T> Request<T> add(Request<T> request) { // 将request和当前的RequestQueue绑定 request.setRequestQueue(this); // 将request添加到set集合中,用于执行 cancelAll() 操作 synchronized (mCurrentRequests) { mCurrentRequests.add(request); } // 给request添加一些标记 request.setSequence(getSequenceNumber()); request.addMarker("add-to-queue"); sendRequestEvent(request, RequestEvent.REQUEST_QUEUED); // 判断request是否需要缓存,对于 Get 以外的请求,默认关闭缓存 if (!request.shouldCache()) { mNetworkQueue.add(request); return request; } mCacheQueue.add(request); return request; } 复制代码
添加 request
的操作算是很简单的了,无非是根据 request
是否需要缓存,将 request
添加到 CacheQueue
或者 NetworkQueue
中。
看到这,我们是不是在想,怎么添加之后就没有动作了?非也非也,我们回顾下,在创建 requestQueue
的同时,是不是创建了5个子线程并且开启了它们,它们内部使用的 CacheQueue
或者 NetworkQueue
不就是上面提到的么。接下来,我们先看看 CacheDispatcher
内部做了什么。
CacheDispatcher 调度器剖析:
CacheDispatcher
就是一个继承了 Thread
的类,我们在执行 RequestQueue.start()
的时候创建了它,并开启了它,现在我们来看看它的 run()
方法:
@Override public void run() { // 设置线程优先级为后台线程 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 实则是执行了DiskBasedCache的initialize()方法 mCache.initialize(); while (true) { try { processRequest(); } catch (InterruptedException e) { // 中断当前线程,并且退出死循环,mQuit在调用CacheDispatcher的quit()方法之后会被赋值为true if (mQuit) { Thread.currentThread().interrupt(); return; } } } } 复制代码
开了一个死循环,然后调用了 processRequest()
, 我们接着看这个方法:
private void processRequest() throws InterruptedException { // 从CacheQueue中取出一个可用的request final Request<?> request = mCacheQueue.take(); processRequest(request); } @VisibleForTesting void processRequest(final Request<?> request) throws InterruptedException { request.addMarker("cache-queue-take"); request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED); try { //request如果被取消了,就直接返回 if (request.isCanceled()) { request.finish("cache-discard-canceled"); return; } Cache.Entry entry = mCache.get(request.getCacheKey()); // 没有缓存就把request添加到NetworkQueue中 if (entry == null) { request.addMarker("cache-miss"); // 没有缓存,并且等待队列中也没有此request,那么就直接加入到NetworkQueue中 if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { mNetworkQueue.put(request); } return; } // 如果缓存过期了,也是一样把request添加到NetworkQueue中 if (entry.isExpired()) { request.addMarker("cache-hit-expired"); request.setCacheEntry(entry); if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { mNetworkQueue.put(request); } return; } // 有缓存并且没有过期 request.addMarker("cache-hit"); // 根据缓存的内容解析 Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); // 是否需要更新 if (!entry.refreshNeeded()) { // 不需要更新,直接将结果调度到主线程 mDelivery.postResponse(request, response); } else { request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); response.intermediate = true; // 判断是否有相同缓存键的任务在执行 if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { // 需要更新结果,先将结果调度到主线程,然后执行new runnable(){} // runnable中就是将request添加到NetworkQueue中,更新一下内容 mDelivery.postResponse( request, response, new Runnable() { @Override public void run() { try { mNetworkQueue.put(request); } catch (InterruptedException e) { // Restore the interrupted status Thread.currentThread().interrupt(); } } }); } else { // request已经加入到mWaitingRequests中 // 直接把结果调度到主线程 mDelivery.postResponse(request, response); } } } finally { request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED); } } 复制代码
我们在 processRequest
中可以看到有一个方法经常出现,那就是 mWaitingRequestManager.maybeAddToWaitingRequests(request)
,它的作用是判断当前这个 request
是否有存在相同缓存键的请求已经处于运行状态,如果有,那么就将这个 request
加入到一个等待队列中,等到相同缓存键的请求完成。
总结一下 CacheDispatcher
主要步骤:
- 从
CacheQueue
中循环取出request
; - 如果缓存丢失,加入到
NetworkQueue
中; - 如果缓存过期,加入到
NetworkQueue
中; - 将缓存中的数据解析成
Response
对象; - 如果不需要更新,直接将结果回调到主线程, 回调操作等介绍完NetworkDispatcher之后一起深入剖析;
- 如果需要更新,先将结果回调到主线程,然后再将
request
加入到NetworkQueue
中。
NetworkDispatcher 调度器剖析:
NetworkDispatcher
和 CacheDispatcher
十分类似,都是 Thread
的子类,下面重点看下它的 run()
方法:
@Override public void run() { // 设置线程优先级为后台线程 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { try { processRequest(); } catch (InterruptedException e) { // 调用quit()方法之后,mQuit就会被赋值true if (mQuit) { Thread.currentThread().interrupt(); return; } } } } 复制代码
继续看 processRequest()
方法:
private void processRequest() throws InterruptedException { // 从NetworkQueue中取出request Request<?> request = mQueue.take(); processRequest(request); } @VisibleForTesting void processRequest(Request<?> request) { long startTimeMs = SystemClock.elapsedRealtime(); request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED); try { request.addMarker("network-queue-take"); // 如果request被取消了,那么就不执行此request if (request.isCanceled()) { request.finish("network-discard-cancelled"); request.notifyListenerResponseNotUsable(); return; } addTrafficStatsTag(request); // 还记得这个mNetwork么,它就是Volley.newRequestQueue()方法里的BasicNetwork对象,一会我们来看看mNetwork.performRequest()方法是如何得到NetworkResponse的 NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete"); // notModified是服务端返回304,hasHadResponseDelivered()是request已经回调过了 if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); request.notifyListenerResponseNotUsable(); return; } // 将NetworkResponse解析成Response对象,在子线程中执行 Response<?> response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); // 将request写入缓存 if (request.shouldCache() && response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } request.markDelivered(); // 回调结果至主线程 mDelivery.postResponse(request, response); 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(); } finally { request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED); } } 复制代码
通过 NetworkDispatcher.run()
方法可以发现,主要分为以下几步:
- 通过
BasicNetwork.performRequest(request)
得到NetworkResponse
对象; - 通过
request.parseNetworkResponse(networkResponse)
解析得到Response
对象; - 通过
mDelivery
将成功结果或者失败结果回调到主线程。
现在我们依次来分析下这三步:
-
请求网络,得到
NetworkResponse
:BasicNetwork
实现了Network
接口,其主要代码集中在NetworkResponse performRequest(Request<?> request) throws VolleyError
中,也就是执行特定的请求。@Override 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 { // 得到请求头信息 Map<String, String> additionalRequestHeaders = getCacheHeaders(request.getCacheEntry()); // 具体的网络请求是靠BaseHttpStack执行的 httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders); int statusCode = httpResponse.getStatusCode(); responseHeaders = httpResponse.getHeaders(); // 下面就是根据不同的状态码返回不同的NetworkResponse对象了,具体就不分析了 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); } } // 省略大部分代码... } 复制代码
通过上面源码可以看出,
BasicNetwork
就是封装了一下NetworkResponse
对象蛮,并没有涉及到网络请求,我们继续深入到BaseHttpStack.executeRequest(request, additionalRequestHeaders)
源码中。public abstract class BaseHttpStack implements HttpStack { public abstract HttpResponse executeRequest( Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError; } 复制代码
我们发现
BaseHttpStack
是一个抽象类,那么具体的请求是在哪呢,不知道你是否还有印象,在Volley.newRequestQueue()
中,我们创建BasicNetwork
的时候是根据 Android 版本传入不同的BaseHttpStack
子类,Android 2.3 以上我们传入的是HurlStack
对象,下面就看看HurlStack.executeRequest()
方法吧。@Override public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { // 得到url String url = request.getUrl(); HashMap<String, String> map = new HashMap<>(); map.putAll(additionalHeaders); // Request.getHeaders() takes precedence over the given additional (cache) headers). map.putAll(request.getHeaders()); ... URL parsedUrl = new URL(url); // 通过HttpURLConnection来请求网络 HttpURLConnection connection = openConnection(parsedUrl, request); } 复制代码
其实
HurlStack.executeRequest()
方法里,就是借助了HttpURLConnection
对象来请求网络,并根据不同的条件返回不同的HttpResponse
对象的。 -
解析
NetworkResponse
, 得到Response
:解析过程是定义在
Request
抽象类的protected abstract Response<T> parseNetworkResponse(NetworkResponse response)
抽象方法中,对于每一种具体的Request
类,比如:StringRequest
、JsonObjectRequest
等等都有自己的实现,下面我们来看看StringRequest
中的parseNetworkResponse()
方法:@Override @SuppressWarnings("DefaultCharset") protected Response<String> parseNetworkResponse(NetworkResponse response) { String parsed; try { // 将response中的data信息取出来 parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { // Since minSdkVersion = 8, we can't call // new String(response.data, Charset.defaultCharset()) // So suppress the warning instead. parsed = new String(response.data); } // 封装成Response对象 return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); } 复制代码
-
回调主线程,如果你看过我之前的Handler源码剖析 文章话,那么这一步就很简单了,我们来理一理:
回调是通过
ResponseDelivery mDelivery
对象来执行的,这个对象最早是在创建RequestQueue()
的时候初始化的,我在那一小节特意标注了,再来回顾下:public RequestQueue(Cache cache, Network network, int threadPoolSize) { this( cache, network, threadPoolSize, // 创建ExecutorDelivery对象,传入一个Handler对象,并且Handler绑定的是主线程的Looper new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } public class ExecutorDelivery implements ResponseDelivery { private final Executor mResponsePoster; /** * ExecutorDelivery构造函数,内部初始化了mResponsePoster接口 * 并且在execute()方法里调用了handler.post()方法 */ public ExecutorDelivery(final Handler handler) { // 初始化Execute对象 mResponsePoster = new Executor() { @Override public void execute(Runnable command) { handler.post(command); } }; } } 复制代码
知道了它的初始化,我们再来看看它是如何实现回调的:
Volley 中回调是通过
postResponse()
方法的 :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)); } 复制代码
postResponse()
最终会调用mResponsePoster
对象的execute()
方法,传入了一个ResponseDeliveryRunnable
对象,它实现了Runnable
接口,execute()
方法会通过Handler.post(runnable)
将ResponseDeliveryRunnable
放入消息队列。最后我们来看看这个ResponseDeliveryRunnable
的run()
方法在主线程中做了什么操作:@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; } if (mResponse.isSuccess()) { // 执行成功的回调,在具体的Request实现类中,比如StringRequest就会调用listener.onResponse(string)回调 mRequest.deliverResponse(mResponse.result); } else { // 执行失败的回调,在request中,直接回调了listener.onErrorResponse(error) mRequest.deliverError(mResponse.error); } // intermediate默认为false,但是在CacheDispatcher的run()中,如果需要更新缓存,那么就会置为true if (mResponse.intermediate) { mRequest.addMarker("intermediate-response"); } else { mRequest.finish("done"); } // 如果传入了runnable不为空,那就就执行runnable.run()方法 // 回忆下在CacheDispatcher的run()方法中,如果request有缓存,但是需要更新缓存的时候,mDelivery是不是调用的带runnable的方法 if (mRunnable != null) { mRunnable.run(); } } 复制代码
分析到这的时候,大家可以借助下方的流程图理一理Volley的整体流程,主要理解一下缓存线程和网络线程的转换,子线程和主线程的转换:
源码分析的文字还在不断的更新,如果本文章你发现的不正确或者不足之处,欢迎你在下方留言或者扫描下方的二维码留言也可!
扫描关注公众号
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 【Java集合源码剖析】ArrayList源码剖析
- Java集合源码剖析:TreeMap源码剖析
- 我的源码阅读之路:redux源码剖析
- ThreadLocal源码深度剖析
- SharedPreferences源码剖析
- 一文深度剖析 Axios 源码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Node即学即用
[英] Tom Hughes-Croucher、[英] Mike Wilson / 郑达韡 / 人民邮电出版社 / 2013-2 / 39.00元
《Node即学即用》由休斯-克劳奇、威尔逊编著,《Node即学即用》讲解如何用Node构建可扩展因特网应用,是全面的实用指南,除了详细介绍Node提供的API外,还用大量篇幅介绍了服务器事件驱动开发的重要概念。内容涉及跨服务器的并发连接、非阻塞I/O和事件驱动的编程、如何支持各种数据库和数据存储工具、NodeAPI的使用示例等。适合对JavaScript及编程有一定程度了解的读者阅读。一起来看看 《Node即学即用》 这本书的介绍吧!