@TryLoveCatch
2021-04-09T10:29:15.000000Z
字数 41107
阅读 2114
android
第三方框架
Volley
Volley适合去进行数据量不大,但通信频繁 的网络操作,例如常见的搜索栏,输入字符自动联想你要输入的单词;而对于大数据量的网络操作,比如下载文件等,Volley 的表现就会非常糟糕,因为Volley将整个response加载到内存并进行操作(可以是解析等操作)大文件可能会引起OOM。
一般而言,网络请求队列都是整个 App 内使用的全局性对象,因此最好写入 Application 类中:
public class MyApplication extends Application{
// 建立请求队列
public static RequestQueue queue;
@Override
public 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>() {
@Override
public void onResponse(String response) {
//这里可以更新UI
}
}, new Response.ErrorListener() {
@Override
public 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) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> map = new HashMap<String, String>();
map.put("params1", "value1");
map.put("params2", "value2");
return map;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError{
// 设置header
}
};
一般来说,Post需要复写StringRequest,也就是Request的getParams(),也可以按需复写getHeaders()。
StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) {
@Override
public 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>() {
@Override
public void onResponse(JSONObject response) {
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
ImageRequest imageRequest = new ImageRequest("http://yydcdut.com/img/avatar.png",
new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
imageView.setImageBitmap(response);
}
}, 0, 0, Config.RGB_565, new Response.ErrorListener() {
@Override
public 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
@Override
public 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 HTTPS
if ("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 HTTPS
if ("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();
}
@Override
public 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() {
@Override
public boolean apply(Request<?> request) {
return request.getTag() == tag;
}
});
}
其实很简单,就是循环遍历,当前的所有的Request,调用对应的cancel()
我们先来看下mCurrentRequests是怎么维护的:
然后来看下Request的cancel():
public void cancel() {
synchronized (mLock) {
mCanceled = true;
mErrorListener = null;
}
}
非常简单,就是改变了mCanceled的值,所以肯定是在请求网络的时候,会来判断这个值,来决定是否请求网络或者返回结果后,是否通知上层。
@Override
public 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() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.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() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
// ....
@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));
}
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;
}
// 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() {
@Override
public 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>() {
@Override
public void onResponse(String response) {
//这里可以更新UI
}
}, new Response.ErrorListener() {
@Override
public 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 GMT
Cache-Control: max-age=600
可以看出来:
Cache-Control优先级比Expires高
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
图中的浏览器缓存,可以换成是Volley缓存即可。
协商缓存相关的header:
// response
last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
etag: W/"175737-152583456000"
// request
if-modified-since: Wed, 21 Oct 2015 07:28:00 GMT
if-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);
}
@Override
public 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缓存到内存中,注意不包含data
CacheHeader 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转换为Entry
return 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 {
// balabala
public 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 {
// balabala
void processRequest(Request<?> request) {
// balabala
if (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 {
// balabala
void processRequest(final Request<?> request) throws InterruptedException {
// balabala
Cache.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;
}
@Override
public 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的装饰类,用于关闭连接。