@TryLoveCatch
2021-04-09T02:29:15.000000Z
字数 41107
阅读 2655
android 第三方框架 Volley
Volley适合去进行数据量不大,但通信频繁 的网络操作,例如常见的搜索栏,输入字符自动联想你要输入的单词;而对于大数据量的网络操作,比如下载文件等,Volley 的表现就会非常糟糕,因为Volley将整个response加载到内存并进行操作(可以是解析等操作)大文件可能会引起OOM。
一般而言,网络请求队列都是整个 App 内使用的全局性对象,因此最好写入 Application 类中:
public class MyApplication extends Application{// 建立请求队列public static RequestQueue queue;@Overridepublic void onCreate() {super.onCreate();queue = Volley.newRequestQueue(getApplicationContext());}public static RequestQueue getHttpQueue() {return queue;}}
这个时候,后台的多个处理线程已经建立了,等待着请求任务。
Volley 提供了JsonObjectRequest、JsonArrayRequest、StringRequest、ImageRequest等 Request 形式
StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {@Overridepublic void onResponse(String response) {//这里可以更新UI}}, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {}});// 设置该请求的标签stringRequest.setTag("stringGet");// 将请求添加到队列中MyApplication.getHttpQueue().add(stringRequest);
将Request加入RequestQueue即可,网络连接就会自动执行了
MyApplication.getHttpQueues.cancelAll("stringGet");
MyApplication.getHttpQueues.cancelAll(RequestFilter);
StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) {@Overrideprotected Map<String, String> getParams() throws AuthFailureError {Map<String, String> map = new HashMap<String, String>();map.put("params1", "value1");map.put("params2", "value2");return map;}@Overridepublic Map<String, String> getHeaders() throws AuthFailureError{// 设置header}};
一般来说,Post需要复写StringRequest,也就是Request的getParams(),也可以按需复写getHeaders()。
StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) {@Overridepublic Map<String, String> getHeaders() throws AuthFailureError {Map<String, String> map = new HashMap<String, String>();map.put("header1", "value1");map.put("header2", "value2");return map;}};
类似于 StringRequest ,JsonRequest 也是继承自 Request ,但是一个抽象类,有两个直接的子类,JsonObjectRequest 和 JsonArrayRequest 。
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("url", null,new Response.Listener<JSONObject>() {@Overridepublic void onResponse(JSONObject response) {}}, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {}});
ImageRequest imageRequest = new ImageRequest("http://yydcdut.com/img/avatar.png",new Response.Listener<Bitmap>() {@Overridepublic void onResponse(Bitmap response) {imageView.setImageBitmap(response);}}, 0, 0, Config.RGB_565, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {imageView.setImageResource(R.drawable.default_image);}});
ImageRequest 的构造函数接收六个参数:
Android Volley 源码解析(一),网络请求的执行流程
Android Volley 源码解析(二),探究缓存机制
Android Volley 源码解析(三),图片加载的实现
首先梳理下,我们使用的流程:
我们按照使用流程来进行分析,基于Volley1.1.1。
我们从 Volley 的使用方法入手,一步一步探究底层的源码实现,我们的入手点就是Volley.newRequestQueue(context)。
Volley类非常简单,主要作用就是构建 RequestQueue
public class Volley {private static final String DEFAULT_CACHE_DIR = "volley";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);}public static RequestQueue newRequestQueue(Context context) {return newRequestQueue(context, (BaseHttpStack) null);}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;}}
一般来说,我们都是调用一个参数的newRequestQueue方法,它里面只是调用了 newRequestQueue() 的方法重载,并给第二个参数传入 null。
我们重点来看下两个参数的newRequestQueue方法:
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);}
现在来说,一般都不建议用HttpClient,再加上现在的 Android 手机基本都是 4.0以上了,所以我们不分析HttpClientStack,只需要关注HurlStack
我们来看这个newRequestQueue(Context context, Network 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;}
我们先来看下HurlStack和BasicNetwork
Hurl是HttpUrlConnection的缩写,顾名思义,它是使用了HttpURLConnection实现网络请求的。
interface HttpStack{}class HttpClientStack implements HttpStack{}class BaseHttpStack implements HttpStack {}class HurlStack extends BaseHttpStack {}
HttpStack和HttpClientStack已经被volley标志为废弃
所以我们会在BaseHttpStack里面看见很诡异的代码:
public abstract class BaseHttpStack implements HttpStack {public abstract HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError;@Deprecated@Overridepublic final org.apache.http.HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {// 巴拉巴拉巴拉巴拉}}
HurlStack继承了BaseHttpStack,实现了BaseHttpStack的executeRequest方法,主要是通过HttpUrlConnection来实现网络请求
HurlStack有多个构造函数,我们来看下:
public HurlStack() {this(null);}public HurlStack(UrlRewriter urlRewriter) {this(urlRewriter, null);}public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {mUrlRewriter = urlRewriter;mSslSocketFactory = sslSocketFactory;}
HurlStack的创建是在Volley的newRequestQueue()里面:
if (Build.VERSION.SDK_INT >= 9) {network = new BasicNetwork(new HurlStack());}
很明显,是调用无参的构造函数,既然已经在代码里面写死了,那么有参的构造函数还有什么用呢?
答案肯定是有用的,我们还是来看Volley的newRequestQueue():
public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {// 吧啦吧啦吧啦}
我们可以传进来啊!!!不一定非的是自定义的Stack,我们可以new 一个HrulStack的有参数版本啊:
HurlStack tHurlStack = new HurlStack(new UrlRewriter(), new SSLSocketFactory());Volley.newRequestQueue(getApplicationContext(), tHurlStack);
public interface UrlRewriter {String rewriteUrl(String originalUrl);}
看接口就明白了,它就是用来重新转换url的
这个很明显,是用来https请求的
这个是实现的父类的方法,也是最重要的方法,我们一点一点来看吧
String url = request.getUrl();HashMap<String, String> map = new HashMap<>();map.putAll(additionalHeaders);map.putAll(request.getHeaders());if (mUrlRewriter != null) {String rewritten = mUrlRewriter.rewriteUrl(url);if (rewritten == null) {throw new IOException("URL blocked by rewriter: " + url);}url = rewritten;}
additionalHeaders是传进来的参数,这个其实就是根据内部cache判断拿到cache相关的header,一般来说就两个值If-None-Match和If-Modified-Since,主要用来做协商缓存的,下面缓存会单独拿出来讲的,我们先有个印象。
request.getHeaders(),这个其实是一个空实现
public Map<String, String> getHeaders() throws AuthFailureError {return Collections.emptyMap();}
这个主要是供子类来实现的,我们可以继承Request来自定义我们自己的XxxxRequest,如果需要Header就需要重写这个方法了,类似的还有post的params
我们继续往下看
URL parsedUrl = new URL(url);HttpURLConnection connection = openConnection(parsedUrl, request);---------private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {HttpURLConnection connection = createConnection(url);int timeoutMs = request.getTimeoutMs();connection.setConnectTimeout(timeoutMs);connection.setReadTimeout(timeoutMs);connection.setUseCaches(false);connection.setDoInput(true);// use caller-provided custom SslSocketFactory, if any, for HTTPSif ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {((HttpsURLConnection) connection).setSSLSocketFactory(mSslSocketFactory);}return connection;}
我们先来看openConnection()里面的createConnection()
protected HttpURLConnection createConnection(URL url) throws IOException {HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setInstanceFollowRedirects(HttpURLConnection.getFollowRedirects());return connection;}
setInstanceFollowRedirects(),默认穿进去的是true,表示直接将重定向交给HttpURLConnection完成,我们完全感知不到重定向的发生。
我们回到openConnection(),来看request.getTimeoutMs(),这个也是一个知识点,回到用Request的方法:
public final int getTimeoutMs() {return getRetryPolicy().getCurrentTimeout();}
而这个getRetryPolicy(),是接口RetryPolicy的默认实现类DefaultRetryPolicy,在Rquest的构造函数里面给赋值的:
setRetryPolicy(new DefaultRetryPolicy());
所以对外提供了setRetryPolicy(),可用来修改RetryPolicy的实现,我们来看下RetryPolicy是什么:
public interface RetryPolicy {// 超时时间int getCurrentTimeout();// 重试次数int getCurrentRetryCount();// 判断是否可以重试,抛异常就不可以void retry(VolleyError error) throws VolleyError;}
三个方法,一目了然,具体可以看下DefaultRetryPolicy的实现,这里就不说了。
默认的,超时时间为2500,重试次数为1
我们可以自定义RetryPolicy,当然最简单的办法就是用DefaultRetryPolicy的有参构造函数
继续看openConnection(),呃,可能你已经忘记了代码了,重新贴一次:
URL parsedUrl = new URL(url);HttpURLConnection connection = openConnection(parsedUrl, request);---------private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {HttpURLConnection connection = createConnection(url);int timeoutMs = request.getTimeoutMs();// 连接主机超时connection.setConnectTimeout(timeoutMs);// 从主机读取数据超时connection.setReadTimeout(timeoutMs);// 不使用缓存connection.setUseCaches(false);// 打开输入流,以便从服务器获取数据connection.setDoInput(true);// use caller-provided custom SslSocketFactory, if any, for HTTPSif ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {((HttpsURLConnection) connection).setSSLSocketFactory(mSslSocketFactory);}return connection;}
所以默认情况下,HttpURLConnection的超时时间都是2500,其他的比较简单,就不说了,https这个也不说了。继续往下
boolean keepConnectionOpen = false;try {for (String headerName : map.keySet()) {connection.setRequestProperty(headerName, map.get(headerName));}setConnectionParametersForRequest(connection, request);// Initialize HttpResponse with data from the HttpURLConnection.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.");}if (!hasResponseBody(request.getMethod(), responseCode)) {return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()));}// Need to keep the connection open until the stream is consumed by the caller. Wrap the// stream such that close() will disconnect the connection.keepConnectionOpen = true;return new HttpResponse(responseCode,convertHeaders(connection.getHeaderFields()),connection.getContentLength(),new UrlConnectionInputStream(connection));} finally {if (!keepConnectionOpen) {connection.disconnect();}}
从上往下来看,先看setConnectionParametersForRequest()方法,
这个方法有两个功能
private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)throws IOException, AuthFailureError {byte[] body = request.getBody();if (body != null) {addBody(connection, request, body);}}private static void addBody(HttpURLConnection connection, Request<?> request, byte[] body)throws IOException {connection.setDoOutput(true);if (!connection.getRequestProperties().containsKey(HttpHeaderParser.HEADER_CONTENT_TYPE)) {connection.setRequestProperty(HttpHeaderParser.HEADER_CONTENT_TYPE, request.getBodyContentType());}DataOutputStream out = new DataOutputStream(connection.getOutputStream());out.write(body);out.close();}
Request里面的getBody()的默认实现就是获取getParams(),获取到参数,然后构建A=xxx这种请求体
然后,获取connection.getResponseCode(),如果是-1,直接抛异常,这个-1主要是connection发生了一些未知错误。
接着,hasResponseBody()来判断Response是否包含数据,根据是否有数据来构建不同的HttpResponse。
HurlStack,就是通过HttpUrlConnection把Request转换为HttpResponse。
输入:Request
输出:HttpResponse
public interface Network {NetworkResponse performRequest(Request<?> request) throws VolleyError;}public class BasicNetwork implements Network {}
BasicNetwork 这个类我觉得,它就是一个用来转换的类,它的作用,说白就是就是输入XXXRequest,输出NetworkResponse:
在 BasicNetwork 中有一个 byte[] 复用池。目的在于不要频繁创建生命周期比较短的 byte[] 对象会使堆频繁分配位置以及在 Android 垃圾回收导致的延时。
Map<String, String> additionalRequestHeaders =getCacheHeaders(request.getCacheEntry());httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
加入缓存信息的header,这个就是HrulStack里面说的缓存header,当时还说了就两个值If-None-Match和If-Modified-Since,为什么呢?我们来看:
private Map<String, String> getCacheHeaders(Cache.Entry entry) {// If there's no cache entry, we're done.if (entry == null) {return Collections.emptyMap();}Map<String, String> headers = new HashMap<>();if (entry.etag != null) {headers.put("If-None-Match", entry.etag);}if (entry.lastModified > 0) {headers.put("If-Modified-Since", HttpHeaderParser.formatEpochAsRfc1123(entry.lastModified));}return headers;}
我们可以看到,这个就是根据Cache.Enrty里面的值得到对应的header。当然这个Cache.Enrty可能为null,当从CacheDispatcher过来的时候不为null,当从NetworkDispatcher过来的时候为null。
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);}// Combine cached and response headers so the response will be complete.List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED,entry.data,/* notModified= */ true,SystemClock.elapsedRealtime() - requestStart,combinedHeaders);}
http返回了304,然后根据Entry,生成NetworkResponse,并返回。
其中combineHeaders()会把cache里面的header和返回的header进行合并。
InputStream inputStream = httpResponse.getContent();if (inputStream != null) {responseContents =inputStreamToBytes(inputStream, httpResponse.getContentLength());} else {// Add 0 byte response as a way of honestly representing a// no-content request.responseContents = new byte[0];}
这个inputStream就是HrulStack里面的UrlConnectionInputStream,获取Response里面的数据。
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;logSlowRequests(requestLifetime, request, responseContents, statusCode);if (statusCode < 200 || statusCode > 299) {throw new IOException();}return new NetworkResponse(statusCode,responseContents,/* notModified= */ false,SystemClock.elapsedRealtime() - requestStart,responseHeaders);
封装NetworkResponse,并返回。
实现了Network,通过这个接口我们就能看出来它的作用:
输入:XXXRequest
输出:NetworkResponse
咱们通过Volley类创建了RequestQueue,其实我们一般使用Volley
的api,大多数都是通过RequestQueue来调用的:
MyApplication.getHttpQueue().add(stringRequest);MyApplication.getHttpQueues.cancelAll("stringGet");MyApplication.getHttpQueues.cancelAll(RequestFilter);
具体的可以参见前面的使用章节
首先来看RequestQueue的构造函数,我们从Volley创建的时候,使用的是:
public RequestQueue(Cache cache, Network network) {this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);}
其中,DEFAULT_NETWORK_THREAD_POOL_SIZE = 4
最终会调用的构造函数是:
public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {mCache = cache;mNetwork = network;mDispatchers = new NetworkDispatcher[threadPoolSize];mDelivery = delivery;}public RequestQueue(Cache cache, Network network, int threadPoolSize) {this(cache,network,threadPoolSize,new ExecutorDelivery(new Handler(Looper.getMainLooper())));}
先记下来这两个类,我们先看RequestQueue的start方法
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();}}
首先先调用了stop(),我们来看下
public void stop() {if (mCacheDispatcher != null) {mCacheDispatcher.quit();}for (final NetworkDispatcher mDispatcher : mDispatchers) {if (mDispatcher != null) {mDispatcher.quit();}}}
调用了CacheDispatcher和NetworkDispatcher的quit(),这个quit方法,两个类的实现是一致的:
public void quit() {mQuit = true;interrupt();}@Overridepublic void run() {Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);while (true) {try {processRequest();} catch (InterruptedException e) {// We may have been interrupted because it was time to quit.if (mQuit) {Thread.currentThread().interrupt();return;}VolleyLog.e("Ignoring spurious interrupt of NetworkDispatcher thread; "+ "use quit() to terminate it");}}}
其实CacheDispatcher和NetworkDispatcher都是继承的Thread,并且这两个里面都是用到BlockingQueue的,所以上面的退出就很好理解了。
好了,言归正传,继续说start()
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();}}
上面说过了,CacheDispatcher 和 NetworkDispatcher 都是继承自 Thread ,所以这里会启动5个线程,等待网络请求的到来,其中 CacheDispatcher 是缓存线程,NetworkDispatcher 是网络请求线程。
接下来,看下add方法:
public <T> Request<T> add(Request<T> request) {// Tag the request as belonging to this queue and add it to the set of current requests.request.setRequestQueue(this);synchronized (mCurrentRequests) {mCurrentRequests.add(request);}// Process requests in the order they are added.request.setSequence(getSequenceNumber());request.addMarker("add-to-queue");// If the request is uncacheable, skip the cache queue and go straight to the network.if (!request.shouldCache()) {mNetworkQueue.add(request);return request;}mCacheQueue.add(request);return request;}
我们在看下cancel()
public void cancelAll(RequestFilter filter) {synchronized (mCurrentRequests) {for (Request<?> request : mCurrentRequests) {if (filter.apply(request)) {request.cancel();}}}}public void cancelAll(final Object tag) {if (tag == null) {throw new IllegalArgumentException("Cannot cancelAll with a null tag");}cancelAll(new RequestFilter() {@Overridepublic boolean apply(Request<?> request) {return request.getTag() == tag;}});}
其实很简单,就是循环遍历,当前的所有的Request,调用对应的cancel()
我们先来看下mCurrentRequests是怎么维护的:
然后来看下Request的cancel():
public void cancel() {synchronized (mLock) {mCanceled = true;mErrorListener = null;}}
非常简单,就是改变了mCanceled的值,所以肯定是在请求网络的时候,会来判断这个值,来决定是否请求网络或者返回结果后,是否通知上层。
@Overridepublic void run() {Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);while (true) {try {processRequest();} catch (InterruptedException e) {// We may have been interrupted because it was time to quit.if (mQuit) {Thread.currentThread().interrupt();return;}VolleyLog.e("Ignoring spurious interrupt of NetworkDispatcher thread; "+ "use quit() to terminate it");}}}
主要方法是processRequest(),会被不断调用
private void processRequest() throws InterruptedException {// Take a request from the queue.Request<?> request = mQueue.take();processRequest(request);}
从队列里面取一个值,如果没有就阻塞等待,如果有,就往下走,调用processRequest(request),这个方法是最重要的方法了,我们分段来看:
if (request.isCanceled()) {request.finish("network-discard-cancelled");request.notifyListenerResponseNotUsable();return;}
发起请求前,如果这个Request已经被cancel,那么就通知,然后return,继续下一次循环
addTrafficStatsTag(request);@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)private void addTrafficStatsTag(Request<?> request) {// Tag the request (if API >= 14)if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());}}
流量监控,给这个请求设置tag,默认是url的host的hashcode
NetworkResponse networkResponse = mNetwork.performRequest(request);request.addMarker("network-http-complete");
调用BasicNetwork来完成网络请求,并得到NetworkResponse返回
if (networkResponse.notModified && request.hasHadResponseDelivered()) {request.finish("not-modified");request.notifyListenerResponseNotUsable();return;}
如果服务器返回 304,而且我们已经分发过该 Request 的结果,那就不用进行第二次分发了。
304代表服务器上的结果跟上次访问的结果是一样的,也就是说数据没有变化。
// Parse the response here on the worker thread.Response<?> response = request.parseNetworkResponse(networkResponse);request.addMarker("network-parse-complete");
调用XxxReqeust的parseNetworkResponse方法,得到Xxx的数据类型,把NetworkResponse,转换为Response
// Write to cache if applicable.if (request.shouldCache() && response.cacheEntry != null) {mCache.put(request.getCacheKey(), response.cacheEntry);request.addMarker("network-cache-written");}
如果该Request允许缓存,就调用DiskBasedCache进行缓存处理。response.cacheEntry 如果为null,就证明这个请求返回的response里面设置来不需要缓存,具体参考 HttpHeaderParser
// Post the response back.request.markDelivered();mDelivery.postResponse(request, response);request.notifyListenerResponseReceived(response);
注意一下,当需要异常情况的时候,都是调用的Request.finish()和Request.notifyListenerResponseNotUsable()
好了,还是总结一下:
- 如果request取消了,就直接“其他逻辑”处理
- 调用BasicNetwork,处理Xxxrequest,得到NetworkResponse
- NetworkResponse返回304,就直接“其他逻辑”处理
- 调用Xxxrequest的parseNetworkResponse(),转换NetworkResponse为Response
- Xxxrequest如果允许缓存,就调用DiskBasedCache缓存起来
- ResponseDelivery分发response,回调notifyListenerResponseReceived()
- 所谓的“其他逻辑”,就是调用Request.finish()和Request.notifyListenerResponseNotUsable()
和NetworkDispatcher基本类似,我们直奔processRequest(final Request request)方法
if (request.isCanceled()) {request.finish("cache-discard-canceled");return;}
如果请求已经取消了,我们直接结束该请求
Cache.Entry entry = mCache.get(request.getCacheKey());if (entry == null) {request.addMarker("cache-miss");// Cache miss; send off to the network dispatcher.if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {mNetworkQueue.put(request);}return;}
从 Cache 中取出包含请求缓存数据的 Entry,如果Entry为null,那么就说明没有cache,然后判断是否之前已经有过同样的Reqeust,如果没有就添加进mNetworkQueue,等待进行网络请求
if (entry.isExpired()) {request.addMarker("cache-hit-expired");request.setCacheEntry(entry);if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {mNetworkQueue.put(request);}return;}
如果cache已经过期,那么就判断是否之前已经有过同样的Reqeust,如果没有就添加进mNetworkQueue,等待进行网络请求
request.addMarker("cache-hit");Response<?> response =request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));request.addMarker("cache-hit-parsed");
走到这里,我们就得到了一个cache,然后使用cache构建一个NetworkResponse,并解析转换为Response
if (!entry.refreshNeeded()) {// Completely unexpired cache hit. Just deliver the response.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() {@Overridepublic void run() {try {mNetworkQueue.put(request);} catch (InterruptedException e) {// Restore the interrupted statusThread.currentThread().interrupt();}}});} else {mDelivery.postResponse(request, response);}
好了,到此为止,CacheDispatcher已经分析晚了,我们可以总结一下:

- 如果cache可用,就直接把缓存封装成response,ResponseDelivery进行分发
- 如果cache需要刷新,就在ResponseDelivery分发完成之后,把当前reqeust放入到network队列中去(当然会通过等待队列进行判断)
- 如果cache不可用,那么就把当前reqeust放入到network队列中去(当然会通过等待队列进行判断)
当缓存不存在、过期、需要刷新的时候,都会调用WaitingRequestManager.maybeAddToWaitingRequests(),也就是说需要将request放到NetworkDispatcher的队列中的时候,都会调用这个方法。
那这个类有什么作用呢?我们理一理
// cacheKey和request的对应关系Map<String, List<Request<?>>> mWaitingRequests = new HashMap<>();void onResponseReceived(Request<?> request, Response<?> response)void onNoUsableResponseReceived(Request<?> request)boolean maybeAddToWaitingRequests(Request<?> request)
我们前面说了很多次ResponseDelivery分发,这个接口就是用来分发response的,他的唯一实现类就是ExecutorDelivery:
public interface ResponseDelivery {void postResponse(Request<?> request, Response<?> response);void postResponse(Request<?> request, Response<?> response, Runnable runnable);void postError(Request<?> request, VolleyError error);}
ExecutorDelivery的代码也比较简单:
public class ExecutorDelivery implements ResponseDelivery {private final Executor mResponsePoster;public ExecutorDelivery(final Handler handler) {mResponsePoster =new Executor() {@Overridepublic void execute(Runnable command) {handler.post(command);}};}// ....@Overridepublic void postResponse(Request<?> request, Response<?> response, Runnable runnable) {request.markDelivered();request.addMarker("post-response");mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));}@Overridepublic void postError(Request<?> request, VolleyError error) {request.addMarker("post-error");Response<?> response = Response.error(error);mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));}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")@Overridepublic void run() {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();}}}}
构造函数根据handler创建了一个Executor:
mResponsePoster =new Executor() {@Overridepublic void execute(Runnable command) {handler.post(command);}};
不太明白这个Executor有什么作用,个人感觉直接用Handler是一模一样的。
这个类里面的所有操作都是在主线程进行的。
我们来看下这个类是在RequestQueue里面创建的:
public RequestQueue(Cache cache, Network network, int threadPoolSize) {this(cache,network,threadPoolSize,new ExecutorDelivery(new Handler(Looper.getMainLooper())));}
主要看下那个内部类Runnable:
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();}
我们可以看到是调用Request来处理Response和Error的,我们看下Request里面:
protected abstract void deliverResponse(T response);public void deliverError(VolleyError error) {Response.ErrorListener listener;synchronized (mLock) {listener = mErrorListener;}if (listener != null) {listener.onErrorResponse(error);}}
deliverResponse()是一个抽象方法,而deliverError的处理很简单,就是调用ErrorListener。
那么我们来看下StringReqeust的实现:
protected void deliverResponse(String response) {Response.Listener<String> listener;synchronized (mLock) {listener = mListener;}if (listener != null) {listener.onResponse(response);}}
也是直接调用Listener,返回结果。
我们会发现
- 我们处理Response的是在Request,调用的parseNetworkResponse()
- 我们分发结果最终也是在Request,调用的传进来的两个Listener
所以我们现在拐回去看下,Volley的使用:
StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {@Overridepublic void onResponse(String response) {//这里可以更新UI}}, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {}});
这两个listener就是最后处理结果的,而如果我们想自定义一个Request,那么就需要实现parseNetworkResponse(),来解析结果。
我们来看下HttpHeaderParser的逻辑:
- 有cache-control
1.1. softTtl = now + maxAge * 1000,当前时间 + 最大过期时间的毫秒值
1.2. 是否存在must-revalidate & proxy-revalidate,也就是强制过期必须请求
1.3. 如果存在,ttl = softTtl
1.4. 如果不存在,ttl = softTtl + 缓存过期后还可以继续使用多久的毫秒值- 没有cache-control,并且存在Date和Expires,且Expires > Date
2.1. softTtl = now + (Expires - Date)
2.2. ttl = softTtl- 都不符合,取默认值,都为0
我们可以从之前说的那张图,来换个角度理解Volley




图中的浏览器缓存,可以换成是Volley缓存即可。
强制缓存相关的header:
Expires: Wed, 21 Oct 2015 07:28:00 GMTCache-Control: max-age=600
可以看出来:
Cache-Control优先级比Expires高
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:


图中的浏览器缓存,可以换成是Volley缓存即可。
协商缓存相关的header:
// responselast-Modified: Wed, 21 Oct 2015 07:28:00 GMTetag: W/"175737-152583456000"// requestif-modified-since: Wed, 21 Oct 2015 07:28:00 GMTif-none-match: W/"175737-152583456000"
Etag / If-None-Match优先级比Last-Modified / If-Modified-Since高
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存,主要过程如下:
Volley 提供了一个 Cache 作为缓存的接口,封装了缓存的实体 Entry,以及一些常规的增删查操作。
public interface Cache {Entry get(String key);void put(String key, Entry entry);void initialize();/*** 使缓存中的 Entry 失效*/void invalidate(String key, boolean fullExpire);void remove(String key);void clear();/*** 用户缓存的实体*/class Entry {public byte[] data;public String etag;public long serverDate;public long lastModified;public long ttl;public long softTtl;public Map<String, String> responseHeaders = Collections.emptyMap();public List<Header> allResponseHeaders;/** 判断 Entry 是否过期. */public boolean isExpired() {return this.ttl < System.currentTimeMillis();}/** 判断 Entry 是否需要刷新. */public boolean refreshNeeded() {return this.softTtl < System.currentTimeMillis();}}}
Entry 作为缓存的实体,主要有三个部分:
Cache 的具体实现类 DiskBaseCache,我们先来看一下它的内部类CacheHeader
static class CacheHeader {long size;final String key;final String etag;final long serverDate;final long lastModified;final long ttl;final long softTtl;final List<Header> allResponseHeaders;private CacheHeader(String key, String etag, long serverDate, long lastModified, long ttl,long softTtl, List<Header> allResponseHeaders) {this.key = key;this.etag = ("".equals(etag)) ? null : etag;this.serverDate = serverDate;this.lastModified = lastModified;this.ttl = ttl;this.softTtl = softTtl;this.allResponseHeaders = allResponseHeaders;}CacheHeader(String key, Entry entry) {this(key, entry.etag, entry.serverDate, entry.lastModified, entry.ttl, entry.softTtl,getAllResponseHeaders(entry));size = entry.data.length;}/*** CacheHeader转换为Entry**/Entry toCacheEntry(byte[] data) {Entry e = new Entry();e.data = data;e.etag = etag;e.serverDate = serverDate;e.lastModified = lastModified;e.ttl = ttl;e.softTtl = softTtl;e.responseHeaders = HttpHeaderParser.toHeaderMap(allResponseHeaders);e.allResponseHeaders = Collections.unmodifiableList(allResponseHeaders);return e;}}
我们可以发现CacheHeader跟 Cache 的 Entry 非常像,是不是会觉得好像有点多余呢?我们具体 来看:
CacheHeader 和 Entry 最大的区别,其实就是是byte[] data,data 代表网络响应的元数据,是返回的内容中最占地方的东西,所以 DiskBaseCache 重新抽象了一个不包含 data 的 CacheHeader,并将其缓存到内存中,而data 部分便存储在磁盘缓存中,这样就能最大程度的利用有限的内存空间,避免OOM了。
BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(file));CacheHeader e = new CacheHeader(key, entry);boolean success = e.writeHeader(fos);// 将 entry.data 写入磁盘中fos.write(entry.data);fos.close();// 将 Cache 缓存到内存中putEntry(key, e);
也就是说,内存缓存里面是不包含data的CacheHeader,而如果需要Entry,会从File里面读取出data数据。
public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {mRootDirectory = rootDirectory;mMaxCacheSizeInBytes = maxCacheSizeInBytes;}public DiskBasedCache(File rootDirectory) {this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);}@Overridepublic synchronized void initialize() {// 如果 mRootDirectroy 不存在,则进行创建if (!mRootDirectory.exists()) {if (!mRootDirectory.mkdirs()) {VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());}return;}File[] files = mRootDirectory.listFiles();if (files == null) {return;}// 遍历 mRootDirectory 中的所有文件for (File file : files) {try {long entrySize = file.length();CountingInputStream cis =new CountingInputStream(new BufferedInputStream(createInputStream(file)), entrySize);try {// 将对应的文件的CacheHeader缓存到内存中,注意不包含dataCacheHeader entry = CacheHeader.readHeader(cis);entry.size = entrySize;putEntry(entry.key, entry);} finally {cis.close();}} catch (IOException e) {file.delete();}}}
如果mRootDirectory下面有文件,就将所有文件读进内存缓存里。
这里说一下这个read,这个是根据write的顺序进行读的,特别的String类型:
public synchronized void put(String key, Entry entry) {// 添加缓存之前,先判断是否需要清理一下pruneIfNeeded(entry.data.length);// 根据key生成唯一名字的文件File file = getFileForKey(key);try {BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(file));CacheHeader e = new CacheHeader(key, entry);// 保存除了data之外的所有数据boolean success = e.writeHeader(fos);if (!success) {fos.close();VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());throw new IOException();}// 接着保存data数据fos.write(entry.data);fos.close();// CacheHeader保存到内存中putEntry(key, e);return;} catch (IOException e) {}// 缓存失败,就删除文件boolean deleted = file.delete();if (!deleted) {VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());}}/*** 是否需要清理磁盘空间**/private void pruneIfNeeded(int neededSpace) {// 如果还够用,就直接 return.if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {return;}if (VolleyLog.DEBUG) {VolleyLog.v("Pruning old cache entries.");}long before = mTotalSize;int prunedFiles = 0;long startTime = SystemClock.elapsedRealtime();Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();// 遍历所有的缓存,开始进行删除while (iterator.hasNext()) {Map.Entry<String, CacheHeader> entry = iterator.next();CacheHeader e = entry.getValue();// 删除磁盘文件boolean deleted = getFileForKey(e.key).delete();if (deleted) {mTotalSize -= e.size;} else {VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", e.key, getFilenameForKey(e.key));}iterator.remove();prunedFiles++;// 如果删除之后,空间已经够用了,就停止循环if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {break;}}if (VolleyLog.DEBUG) {VolleyLog.v("pruned %d files, %d bytes, %d ms", prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);}}
需要注意的就是这个mEntries是LinkedHashMap,采用LRU算法。
另外,关于唯一key的生成是这样的:
private String getFilenameForKey(String key) {int firstHalfLength = key.length() / 2;String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());return localFilename;}
将网络请求的 Url 分成两半,然后将这两部分的 hashCode 拼接成缓存 key。Volley 之所以要这样做,主要是为了尽量避免 hashCode 重复造成的文件名重复,求两次 hashCode 都与另外一个 Url 相同的概率比只求一次要小很多。
public synchronized Entry get(String key) {CacheHeader entry = mEntries.get(key);if (entry == null) {return null;}File file = getFileForKey(key);try {CountingInputStream cis =new CountingInputStream(new BufferedInputStream(createInputStream(file)), file.length());try {// 为啥再次读一次呢?CacheHeader entryOnDisk = CacheHeader.readHeader(cis);if (!TextUtils.equals(key, entryOnDisk.key)) {VolleyLog.d("%s: key=%s, found=%s", file.getAbsolutePath(), key, entryOnDisk.key);removeEntry(key);return null;}// 读去data数据byte[] data = streamToBytes(cis, cis.bytesRemaining());// CacheHeader转换为Entryreturn entry.toCacheEntry(data);} finally {cis.close();}} catch (IOException e) {VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());remove(key);return null;}}
为啥再次读一次呢?
为了能正确读到data。因为这个File的前面是其他信息,需要把这些信息读完,才能够读到data。
我们是在Volley#newRequestQueue()创建了DiskBasedCache:
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;}
默认的内存缓存空间是 5M
public class CacheDispatcher extends Thread {// balabalapublic void run() {Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);// 这里!!!!mCache.initialize();while (true) {try {processRequest();} catch (InterruptedException e) {if (mQuit) {Thread.currentThread().interrupt();return;}}}}// balabala}
initialize() 是在 CacheDispatcher 中的 run 方法进行调用的,因为初始化的时候会有很多io操作,从磁盘读取缓存到内存,所以在子线程里面执行
public class NetworkDispatcher extends Thread {// balabalavoid processRequest(Request<?> request) {// balabalaif (request.shouldCache() && response.cacheEntry != null) {mCache.put(request.getCacheKey(), response.cacheEntry);request.addMarker("network-cache-written");}// balabala}// balabala}
put() 方法是在 NetworkDispatcher 中进行调用的,网络请求成功之后,就会根据情况来保存缓存。response.cacheEntry是在HttpHeaderParser解析的,在Reponse里面封装的。
public class CacheDispatcher extends Thread {// balabalavoid processRequest(final Request<?> request) throws InterruptedException {// balabalaCache.Entry entry = mCache.get(request.getCacheKey());// balabala}// balabala}
get()方法也是在CacheDispatcher的 run() 方法中调用。当有请求到来时,便先根据请求的 Url 拿出对应的缓存在内存中的 Entry,然后对 Entry 进行一些判断和处理,最后将其构建成 Response 回调出去。
https://www.cnblogs.com/dasusu/p/9837950.html
https://blog.csdn.net/chunqiuwei/article/details/78003929
https://www.cnblogs.com/dasusu/p/9837950.html
在 BasicNetwork 中有一个 byte[] 复用池。目的在于不要频繁创建生命周期比较短的 byte[] 对象会使堆频繁分配位置以及在 Android 垃圾回收导致的延时。
static class UrlConnectionInputStream extends FilterInputStream {private final HttpURLConnection mConnection;UrlConnectionInputStream(HttpURLConnection connection) {super(inputStreamFromConnection(connection));mConnection = connection;}@Overridepublic void close() throws IOException {super.close();mConnection.disconnect();}}/*** Initializes an {@link InputStream} from the given {@link HttpURLConnection}.** @param connection* @return an HttpEntity populated with data from <code>connection</code>.*/private static InputStream inputStreamFromConnection(HttpURLConnection connection) {InputStream inputStream;try {inputStream = connection.getInputStream();} catch (IOException ioe) {inputStream = connection.getErrorStream();}return inputStream;}
它是HttpURLConnection的InputStream的装饰类,用于关闭连接。