Volley
源码分析三部曲Volley 源码解析之网络请求 Volley 源码解析之图片请求 Volley 源码解析之缓存机制
Volley
是 Google
推出的一款网络通信框架,非常适合数据量小、通信频繁的网络请求,支持并发、缓存和容易扩展、调试等;不过不太适合下载大文件、大量数据的网络请求,因为 Volley
在解析期间将响应放到内存中,我们可以使用 Okhttp
或者系统提供的DownloadManager
来下载文件。
一、简单使用 首先在工程引入volley的library:
1 2 3 dependencies { implementation 'com.android.volley:volley:1.1.1' }
然后需要我们打开网络权限,我这里直接贴出官网简单请求的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 final TextView mTextView = (TextView) findViewById(R.id.text);RequestQueue queue = Volley.newRequestQueue(this ); String url ="http://www.google.com" ; StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse (String response) { mTextView.setText("Response is: " + response.substring(0 ,500 )); } }, new Response.ErrorListener() { @Override public void onErrorResponse (VolleyError error) { mTextView.setText("That didn't work!" ); } }); queue.add(stringRequest);
使用相对简单,回调直接在主线程,我们取消某个请求直接这样操作:
定义一个标记添加到requests中
1 2 3 4 5 6 7 8 9 public static final String TAG = "MyTag" ;StringRequest stringRequest; RequestQueue mRequestQueue; stringRequest.setTag(TAG); mRequestQueue.add(stringRequest);
然后我们可以在 onStop() 中取消所有标记的请求
1 2 3 4 5 6 7 @Override protected void onStop () { super .onStop(); if (mRequestQueue != null ) { mRequestQueue.cancelAll(TAG); } }
二、源码分析 我们先从Volley这个类入手:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public static RequestQueue newRequestQueue (Context context, BaseHttpStack stack) { BasicNetwork network; if (stack == null ) { if (Build.VERSION.SDK_INT >= 9 ) { network = new BasicNetwork(new HurlStack()); } else { String userAgent = "volley/0" ; try { String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0 ); userAgent = packageName + "/" + info.versionCode; } catch (NameNotFoundException e) { } network = new BasicNetwork( new HttpClientStack(AndroidHttpClient.newInstance(userAgent))); } } else { network = new BasicNetwork(stack); } return newRequestQueue(context, network); } 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; } public static RequestQueue newRequestQueue (Context context) { return newRequestQueue(context, (BaseHttpStack) null ); }
当我们传递一个 Context
的时候,首先为 BaseHttpStack
为 null
,会执行到创建 BaseHttpStack
,BaseHttpStack
是一个网络具体的处理请求,Volley
默认提供了基于 HttpURLCollection
的 HurlStack
和基于HttpClient
的 HttpClientStack
。Android6.0 移除了 HttpClient
,Google
官方推荐使用HttpURLCollection
类作为替换。所以这里在API大于9的版本是用的是 HurlStack
,为什么这样选择,详情可见这篇博客Android访问网络,使用HttpURLConnection还是HttpClient? 。我们使用的是默认的构造,BaseHttpStack
传入为 null
,如果我们想使用自定义的 okhttp
替换底层,我们直接继承 HttpStack
重写即可,也可以自定义 Network
和 RequestQueue
,Volley
的高扩展性充分体现。接下来则创建一个 Network
对象,然后实例化 RequestQueue
,首先创建了一个用于缓存的文件夹,然后创建了一个磁盘缓存,将文件缓存到指定目录的硬盘上,默认大小是5M,但是大小可以配置。接下来调用 RequestQueue
的 start()
方法进行启动,我们进入这个方法查看一下:
1 2 3 4 5 6 7 8 9 10 11 12 public void start () { stop(); 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(); } }
开始启动的时候先停止所有的请求线程和网络缓存线程,然后实例化一个缓存线程并运行,然后一个循环开启DEFAULT_NETWORK_THREAD_POOL_SIZE
(4)个网络请求线程并运行,一共就是5个线程在后台运行,不断的等待网络请求的到来。
构造了 RequestQueue
之后,我们调用 add()
方法将相应的 Request
传入就开始执行网络请求了,我们看看这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public <T> Request<T> add (Request<T> request) { request.setRequestQueue(this ); synchronized (mCurrentRequests) { mCurrentRequests.add(request); } request.setSequence(getSequenceNumber()); request.addMarker("add-to-queue" ); if (!request.shouldCache()) { mNetworkQueue.add(request); return request; } mCacheQueue.add(request); return request; }
关键地方都写了注释,主要作用就是将请求加到请求队列,执行网络请求或者从缓存中获取结果。网络和缓存的请求都是一个优先级阻塞队列,按照优先级出队。上面几个关键步骤,添加到请求集合里面还有设置优先级以及添加到缓存和请求队列都是线程安全的,要么加锁,要么使用线程安全的队列或者原子操作。
接下来我们看看添加到 CacheDispatcher
缓存请求队列的 run
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override public void run () { if (DEBUG) VolleyLog.v("start new dispatcher" ); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); mCache.initialize(); while (true ) { try { processRequest(); } catch (InterruptedException e) { if (mQuit) { Thread.currentThread().interrupt(); return ; } VolleyLog.e( "Ignoring spurious interrupt of CacheDispatcher thread; " + "use quit() to terminate it" ); } } }
接下来的重点是看看 processRequest()
这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 private void processRequest () throws InterruptedException { final Request<?> request = mCacheQueue.take(); processRequest(request); } @VisibleForTesting void processRequest (final Request<?> request) throws InterruptedException { request.addMarker("cache-queue-take" ); if (request.isCanceled()) { request.finish("cache-discard-canceled" ); return ; } Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null ) { request.addMarker("cache-miss" ); if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { mNetworkQueue.put(request); } return ; } 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)) { mDelivery.postResponse( request, response, new Runnable() { @Override public void run () { try { mNetworkQueue.put(request); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); } else { mDelivery.postResponse(request, response); } } }
这部分主要是对请求的缓存判断,是否过期以及需要刷新缓存。我们调用取消所有请求或者取消某个请求实质上就是对 mCanceled
这个变量赋值,然后在缓存线程或者网络线程里面都回去判断这个值,就完成了取消。上面的 isExpired
和 refreshNeeded
,两个区别就是,前者如果过期就直接请求最新的内容,后者就是还在新鲜的时间内,但是把内容返回给用户还是会发起请求,两者一个与 ttl
值相比,另一个与 softTtl
相比。
其中有一个 WaitingRequestManager
,如果有相同的请求那么就需要一个暂存的地方,这个类就是做的这个操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 private static class WaitingRequestManager implements Request .NetworkRequestCompleteListener { private final Map<String, List<Request<?>>> mWaitingRequests = new HashMap<>(); private final CacheDispatcher mCacheDispatcher; WaitingRequestManager(CacheDispatcher cacheDispatcher) { mCacheDispatcher = cacheDispatcher; } @Override public void onResponseReceived (Request<?> request, Response<?> response) { if (response.cacheEntry == null || response.cacheEntry.isExpired()) { onNoUsableResponseReceived(request); return ; } String cacheKey = request.getCacheKey(); List<Request<?>> waitingRequests; synchronized (this ) { waitingRequests = mWaitingRequests.remove(cacheKey); } if (waitingRequests != null ) { if (VolleyLog.DEBUG) { VolleyLog.v( "Releasing %d waiting requests for cacheKey=%s." , waitingRequests.size(), cacheKey); } for (Request<?> waiting : waitingRequests) { mCacheDispatcher.mDelivery.postResponse(waiting, response); } } } @Override public synchronized void onNoUsableResponseReceived (Request<?> request) { String cacheKey = request.getCacheKey(); List<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey); if (waitingRequests != null && !waitingRequests.isEmpty()) { if (VolleyLog.DEBUG) { VolleyLog.v( "%d waiting requests for cacheKey=%s; resend to network" , waitingRequests.size(), cacheKey); } Request<?> nextInLine = waitingRequests.remove(0 ); mWaitingRequests.put(cacheKey, waitingRequests); nextInLine.setNetworkRequestCompleteListener(this ); try { mCacheDispatcher.mNetworkQueue.put(nextInLine); } catch (InterruptedException iex) { VolleyLog.e("Couldn't add request to queue. %s" , iex.toString()); Thread.currentThread().interrupt(); mCacheDispatcher.quit(); } } } private synchronized boolean maybeAddToWaitingRequests (Request<?> request) { String cacheKey = request.getCacheKey(); if (mWaitingRequests.containsKey(cacheKey)) { List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); if (stagedRequests == null ) { stagedRequests = new ArrayList<>(); } request.addMarker("waiting-for-response" ); stagedRequests.add(request); mWaitingRequests.put(cacheKey, stagedRequests); if (VolleyLog.DEBUG) { VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold." , cacheKey); } return true ; } else { mWaitingRequests.put(cacheKey, null ); request.setNetworkRequestCompleteListener(this ); if (VolleyLog.DEBUG) { VolleyLog.d("new request, sending to network %s" , cacheKey); } return false ; } } }
这个类主要是避免相同的请求多次请求,而且在 NetworkDispatcher
里面也会通过这个接口回调相应的值在这里执行,最终比如在网络请求返回304、请求取消或者异常那么都会在这里来处理,如果收到响应则会把值回调给用户,后面的请求也不会再去请求,如果无效的响应则会做一些释放等待的请求操作,请求完成也会将后面相同的请求回调给用户,三个方法都在不同的地方发挥作用。
我们接下来看看 NetworkDispatcher
网络请求队列的 run
方法中的 processRequest
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @VisibleForTesting void processRequest (Request<?> request) { long startTimeMs = SystemClock.elapsedRealtime(); try { request.addMarker("network-queue-take" ); if (request.isCanceled()) { request.finish("network-discard-cancelled" ); request.notifyListenerResponseNotUsable(); return ; } addTrafficStatsTag(request); NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete" ); if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified" ); request.notifyListenerResponseNotUsable(); return ; } 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(); mDelivery.postResponse(request, response); request.notifyListenerResponseReceived(response); } catch (VolleyError volleyError) { ... } }
这里才是网络请求的真正执行以及解析分发的地方,重点看两个地方的代码,执行和解析,我们先看看执行网络请求这个代码,执行的地方是 BasicNetwork.performRequest
,下面看看这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 @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()); httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders); int statusCode = httpResponse.getStatusCode(); responseHeaders = httpResponse.getHeaders(); if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) { Entry entry = request.getCacheEntry(); if (entry == null ) { return new NetworkResponse( HttpURLConnection.HTTP_NOT_MODIFIED, null , true , SystemClock.elapsedRealtime() - requestStart, responseHeaders); } List<Header> combinedHeaders = combineHeaders(responseHeaders, entry); return new NetworkResponse( HttpURLConnection.HTTP_NOT_MODIFIED, entry.data, true , SystemClock.elapsedRealtime() - requestStart, combinedHeaders); } InputStream inputStream = httpResponse.getContent(); if (inputStream != null ) { responseContents = inputStreamToBytes(inputStream, httpResponse.getContentLength()); } else { responseContents = new byte [0 ]; } long requestLifetime = SystemClock.elapsedRealtime() - requestStart; logSlowRequests(requestLifetime, request, responseContents, statusCode); if (statusCode < 200 || statusCode > 299 ) { throw new IOException(); } return new NetworkResponse( statusCode, responseContents, false , SystemClock.elapsedRealtime() - requestStart, responseHeaders); } catch (SocketTimeoutException e) { } } }
这里主要执行了添加缓存头并发起网络请求,然后将返回值组装成一个 NetworkResponse
值返回,接下来我们看看是如何解析这个值的,解析是由 Request
的子类去实现的,我们就看系统提供的 StringRequest
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override @SuppressWarnings ("DefaultCharset" )protected Response<String> parseNetworkResponse (NetworkResponse response) { String parsed; try { parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); }
我们可以看到将值组装成一个String,然后组装成一个 Response
返回,接下来看看这里如何将这个值回调给用户的这个方法mDelivery.postResponse(request, response)
,这里我们先重点看看这个类 ExecutorDelivery
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 public class ExecutorDelivery implements ResponseDelivery { private final Executor mResponsePoster; public ExecutorDelivery (final Handler handler) { mResponsePoster = new Executor() { @Override public void execute (Runnable command) { handler.post(command); } }; } 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 )); } @SuppressWarnings ("rawtypes" ) private static 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 (mRequest.isCanceled()) { mRequest.finish("canceled-at-delivery" ); return ; } if (mResponse.isSuccess()) { 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(); } } } }
上面方法主要是将值回调给用户,那么整个网络请求大致就完成了,其中还涉及很多细节的东西,但是大致流程是走通了,不得不说这个库有很多值得我们学习的地方。
三、总结 现在我们看官网的一张图,总结一下整个流程:
我们可以看到首先是请求添加到 RequestQueue
里,首先是添加到缓存队列,然后查看是否已经缓存,如果有并且在有效期内的缓存直接回调给用户,如果没有查找到,那么则需要添加到网络请求队列重新请求并且解析响应、写入缓存在发送到主线程给用户回调。
参考以及相关链接