[关闭]
@TryLoveCatch 2021-04-09T02:29:15.000000Z 字数 41107 阅读 1713

Android第三方框架之Volley

android 第三方框架 Volley


Volley

Volley适合去进行数据量不大,但通信频繁 的网络操作,例如常见的搜索栏,输入字符自动联想你要输入的单词;而对于大数据量的网络操作,比如下载文件等,Volley 的表现就会非常糟糕,因为Volley将整个response加载到内存并进行操作(可以是解析等操作)大文件可能会引起OOM。

用法

Android:Volley 的使用及其工具类的封装

创建 RequestQueue 对象

一般而言,网络请求队列都是整个 App 内使用的全局性对象,因此最好写入 Application 类中:

  1. public class MyApplication extends Application{
  2. // 建立请求队列
  3. public static RequestQueue queue;
  4. @Override
  5. public void onCreate() {
  6. super.onCreate();
  7. queue = Volley.newRequestQueue(getApplicationContext());
  8. }
  9. public static RequestQueue getHttpQueue() {
  10. return queue;
  11. }
  12. }

这个时候,后台的多个处理线程已经建立了,等待着请求任务。

创建 XXXRequest 对象并添加到请求队列中

Volley 提供了JsonObjectRequest、JsonArrayRequest、StringRequest、ImageRequest等 Request 形式

  1. StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
  2. @Override
  3. public void onResponse(String response) {
  4. //这里可以更新UI
  5. }
  6. }, new Response.ErrorListener() {
  7. @Override
  8. public void onErrorResponse(VolleyError error) {
  9. }
  10. });
  11. // 设置该请求的标签
  12. stringRequest.setTag("stringGet");
  13. // 将请求添加到队列中
  14. MyApplication.getHttpQueue().add(stringRequest);

将Request加入RequestQueue即可,网络连接就会自动执行了

关闭特定标签的网络请求

  1. MyApplication.getHttpQueues.cancelAll("stringGet");

取消这个队列里的所有请求

  1. MyApplication.getHttpQueues.cancelAll(RequestFilter);

Post请求

  1. StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) {
  2. @Override
  3. protected Map<String, String> getParams() throws AuthFailureError {
  4. Map<String, String> map = new HashMap<String, String>();
  5. map.put("params1", "value1");
  6. map.put("params2", "value2");
  7. return map;
  8. }
  9. @Override
  10. public Map<String, String> getHeaders() throws AuthFailureError{
  11. // 设置header
  12. }
  13. };

一般来说,Post需要复写StringRequest,也就是Request的getParams(),也可以按需复写getHeaders()。

Http Header

  1. StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) {
  2. @Override
  3. public Map<String, String> getHeaders() throws AuthFailureError {
  4. Map<String, String> map = new HashMap<String, String>();
  5. map.put("header1", "value1");
  6. map.put("header2", "value2");
  7. return map;
  8. }
  9. };

JsonRequest

类似于 StringRequest ,JsonRequest 也是继承自 Request ,但是一个抽象类,有两个直接的子类,JsonObjectRequest 和 JsonArrayRequest 。

  1. JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("url", null,
  2. new Response.Listener<JSONObject>() {
  3. @Override
  4. public void onResponse(JSONObject response) {
  5. }
  6. }, new Response.ErrorListener() {
  7. @Override
  8. public void onErrorResponse(VolleyError error) {
  9. }
  10. });

ImageRequest

  1. ImageRequest imageRequest = new ImageRequest("http://yydcdut.com/img/avatar.png",
  2. new Response.Listener<Bitmap>() {
  3. @Override
  4. public void onResponse(Bitmap response) {
  5. imageView.setImageBitmap(response);
  6. }
  7. }, 0, 0, Config.RGB_565, new Response.ErrorListener() {
  8. @Override
  9. public void onErrorResponse(VolleyError error) {
  10. imageView.setImageResource(R.drawable.default_image);
  11. }
  12. });

ImageRequest 的构造函数接收六个参数:

源码解析

Android Volley 源码解析(一),网络请求的执行流程
Android Volley 源码解析(二),探究缓存机制
Android Volley 源码解析(三),图片加载的实现

首先梳理下,我们使用的流程:

我们按照使用流程来进行分析,基于Volley1.1.1。

Volley类

我们从 Volley 的使用方法入手,一步一步探究底层的源码实现,我们的入手点就是Volley.newRequestQueue(context)。
Volley类非常简单,主要作用就是构建 RequestQueue

  1. public class Volley {
  2. private static final String DEFAULT_CACHE_DIR = "volley";
  3. public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
  4. BasicNetwork network;
  5. if (stack == null) {
  6. if (Build.VERSION.SDK_INT >= 9) {
  7. network = new BasicNetwork(new HurlStack());
  8. } else {
  9. String userAgent = "volley/0";
  10. try {
  11. String packageName = context.getPackageName();
  12. PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
  13. userAgent = packageName + "/" + info.versionCode;
  14. } catch (NameNotFoundException e) {
  15. }
  16. network = new BasicNetwork(new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
  17. }
  18. } else {
  19. network = new BasicNetwork(stack);
  20. }
  21. return newRequestQueue(context, network);
  22. }
  23. public static RequestQueue newRequestQueue(Context context) {
  24. return newRequestQueue(context, (BaseHttpStack) null);
  25. }
  26. private static RequestQueue newRequestQueue(Context context, Network network) {
  27. File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
  28. RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
  29. queue.start();
  30. return queue;
  31. }
  32. }

一般来说,我们都是调用一个参数的newRequestQueue方法,它里面只是调用了 newRequestQueue() 的方法重载,并给第二个参数传入 null。

我们重点来看下两个参数的newRequestQueue方法:

  1. public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
  2. BasicNetwork network;
  3. if (stack == null) {
  4. if (Build.VERSION.SDK_INT >= 9) {
  5. network = new BasicNetwork(new HurlStack());
  6. } else {
  7. String userAgent = "volley/0";
  8. try {
  9. String packageName = context.getPackageName();
  10. PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
  11. userAgent = packageName + "/" + info.versionCode;
  12. } catch (NameNotFoundException e) {
  13. }
  14. network = new BasicNetwork(new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
  15. }
  16. } else {
  17. network = new BasicNetwork(stack);
  18. }
  19. return newRequestQueue(context, network);
  20. }

现在来说,一般都不建议用HttpClient,再加上现在的 Android 手机基本都是 4.0以上了,所以我们不分析HttpClientStack,只需要关注HurlStack

我们来看这个newRequestQueue(Context context, Network network)方法:

  1. private static RequestQueue newRequestQueue(Context context, Network network) {
  2. File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
  3. RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
  4. queue.start();
  5. return queue;
  6. }

我们先来看下HurlStack和BasicNetwork

HurlStack

Hurl是HttpUrlConnection的缩写,顾名思义,它是使用了HttpURLConnection实现网络请求的。

  1. interface HttpStack{}
  2. class HttpClientStack implements HttpStack{}
  3. class BaseHttpStack implements HttpStack {}
  4. class HurlStack extends BaseHttpStack {}

HttpStack和HttpClientStack已经被volley标志为废弃

所以我们会在BaseHttpStack里面看见很诡异的代码:

  1. public abstract class BaseHttpStack implements HttpStack {
  2. public abstract HttpResponse executeRequest(
  3. Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError;
  4. @Deprecated
  5. @Override
  6. public final org.apache.http.HttpResponse performRequest(
  7. Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
  8. // 巴拉巴拉巴拉巴拉
  9. }
  10. }

HurlStack继承了BaseHttpStack,实现了BaseHttpStack的executeRequest方法,主要是通过HttpUrlConnection来实现网络请求

构造函数

HurlStack有多个构造函数,我们来看下:

  1. public HurlStack() {
  2. this(null);
  3. }
  4. public HurlStack(UrlRewriter urlRewriter) {
  5. this(urlRewriter, null);
  6. }
  7. public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
  8. mUrlRewriter = urlRewriter;
  9. mSslSocketFactory = sslSocketFactory;
  10. }

HurlStack的创建是在Volley的newRequestQueue()里面:

  1. if (Build.VERSION.SDK_INT >= 9) {
  2. network = new BasicNetwork(new HurlStack());
  3. }

很明显,是调用无参的构造函数,既然已经在代码里面写死了,那么有参的构造函数还有什么用呢?
答案肯定是有用的,我们还是来看Volley的newRequestQueue():

  1. public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
  2. // 吧啦吧啦吧啦
  3. }

我们可以传进来啊!!!不一定非的是自定义的Stack,我们可以new 一个HrulStack的有参数版本啊:

  1. HurlStack tHurlStack = new HurlStack(new UrlRewriter(), new SSLSocketFactory());
  2. Volley.newRequestQueue(getApplicationContext(), tHurlStack);
UrlRewriter
  1. public interface UrlRewriter {
  2. String rewriteUrl(String originalUrl);
  3. }

看接口就明白了,它就是用来重新转换url的

SSLSocketFactory

这个很明显,是用来https请求的

executeRequest

这个是实现的父类的方法,也是最重要的方法,我们一点一点来看吧

  1. String url = request.getUrl();
  2. HashMap<String, String> map = new HashMap<>();
  3. map.putAll(additionalHeaders);
  4. map.putAll(request.getHeaders());
  5. if (mUrlRewriter != null) {
  6. String rewritten = mUrlRewriter.rewriteUrl(url);
  7. if (rewritten == null) {
  8. throw new IOException("URL blocked by rewriter: " + url);
  9. }
  10. url = rewritten;
  11. }

additionalHeaders是传进来的参数,这个其实就是根据内部cache判断拿到cache相关的header,一般来说就两个值If-None-Match和If-Modified-Since,主要用来做协商缓存的,下面缓存会单独拿出来讲的,我们先有个印象。

request.getHeaders(),这个其实是一个空实现

  1. public Map<String, String> getHeaders() throws AuthFailureError {
  2. return Collections.emptyMap();
  3. }

这个主要是供子类来实现的,我们可以继承Request来自定义我们自己的XxxxRequest,如果需要Header就需要重写这个方法了,类似的还有post的params

我们继续往下看

  1. URL parsedUrl = new URL(url);
  2. HttpURLConnection connection = openConnection(parsedUrl, request);
  3. ---------
  4. private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
  5. HttpURLConnection connection = createConnection(url);
  6. int timeoutMs = request.getTimeoutMs();
  7. connection.setConnectTimeout(timeoutMs);
  8. connection.setReadTimeout(timeoutMs);
  9. connection.setUseCaches(false);
  10. connection.setDoInput(true);
  11. // use caller-provided custom SslSocketFactory, if any, for HTTPS
  12. if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
  13. ((HttpsURLConnection) connection).setSSLSocketFactory(mSslSocketFactory);
  14. }
  15. return connection;
  16. }

我们先来看openConnection()里面的createConnection()

  1. protected HttpURLConnection createConnection(URL url) throws IOException {
  2. HttpURLConnection connection = (HttpURLConnection) url.openConnection();
  3. connection.setInstanceFollowRedirects(HttpURLConnection.getFollowRedirects());
  4. return connection;
  5. }

setInstanceFollowRedirects(),默认穿进去的是true,表示直接将重定向交给HttpURLConnection完成,我们完全感知不到重定向的发生。

DefaultRetryPolicy

我们回到openConnection(),来看request.getTimeoutMs(),这个也是一个知识点,回到用Request的方法:

  1. public final int getTimeoutMs() {
  2. return getRetryPolicy().getCurrentTimeout();
  3. }

而这个getRetryPolicy(),是接口RetryPolicy的默认实现类DefaultRetryPolicy,在Rquest的构造函数里面给赋值的:

  1. setRetryPolicy(new DefaultRetryPolicy());

所以对外提供了setRetryPolicy(),可用来修改RetryPolicy的实现,我们来看下RetryPolicy是什么:

  1. public interface RetryPolicy {
  2. // 超时时间
  3. int getCurrentTimeout();
  4. // 重试次数
  5. int getCurrentRetryCount();
  6. // 判断是否可以重试,抛异常就不可以
  7. void retry(VolleyError error) throws VolleyError;
  8. }

三个方法,一目了然,具体可以看下DefaultRetryPolicy的实现,这里就不说了。
默认的,超时时间为2500,重试次数为1

我们可以自定义RetryPolicy,当然最简单的办法就是用DefaultRetryPolicy的有参构造函数


继续看openConnection(),呃,可能你已经忘记了代码了,重新贴一次:

  1. URL parsedUrl = new URL(url);
  2. HttpURLConnection connection = openConnection(parsedUrl, request);
  3. ---------
  4. private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
  5. HttpURLConnection connection = createConnection(url);
  6. int timeoutMs = request.getTimeoutMs();
  7. // 连接主机超时
  8. connection.setConnectTimeout(timeoutMs);
  9. // 从主机读取数据超时
  10. connection.setReadTimeout(timeoutMs);
  11. // 不使用缓存
  12. connection.setUseCaches(false);
  13. // 打开输入流,以便从服务器获取数据
  14. connection.setDoInput(true);
  15. // use caller-provided custom SslSocketFactory, if any, for HTTPS
  16. if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
  17. ((HttpsURLConnection) connection).setSSLSocketFactory(mSslSocketFactory);
  18. }
  19. return connection;
  20. }

所以默认情况下,HttpURLConnection的超时时间都是2500,其他的比较简单,就不说了,https这个也不说了。继续往下

  1. boolean keepConnectionOpen = false;
  2. try {
  3. for (String headerName : map.keySet()) {
  4. connection.setRequestProperty(headerName, map.get(headerName));
  5. }
  6. setConnectionParametersForRequest(connection, request);
  7. // Initialize HttpResponse with data from the HttpURLConnection.
  8. int responseCode = connection.getResponseCode();
  9. if (responseCode == -1) {
  10. // -1 is returned by getResponseCode() if the response code could not be retrieved.
  11. // Signal to the caller that something was wrong with the connection.
  12. throw new IOException("Could not retrieve response code from HttpUrlConnection.");
  13. }
  14. if (!hasResponseBody(request.getMethod(), responseCode)) {
  15. return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()));
  16. }
  17. // Need to keep the connection open until the stream is consumed by the caller. Wrap the
  18. // stream such that close() will disconnect the connection.
  19. keepConnectionOpen = true;
  20. return new HttpResponse(
  21. responseCode,
  22. convertHeaders(connection.getHeaderFields()),
  23. connection.getContentLength(),
  24. new UrlConnectionInputStream(connection));
  25. } finally {
  26. if (!keepConnectionOpen) {
  27. connection.disconnect();
  28. }
  29. }

从上往下来看,先看setConnectionParametersForRequest()方法,
这个方法有两个功能

  1. private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
  2. throws IOException, AuthFailureError {
  3. byte[] body = request.getBody();
  4. if (body != null) {
  5. addBody(connection, request, body);
  6. }
  7. }
  8. private static void addBody(HttpURLConnection connection, Request<?> request, byte[] body)
  9. throws IOException {
  10. connection.setDoOutput(true);
  11. if (!connection.getRequestProperties().containsKey(HttpHeaderParser.HEADER_CONTENT_TYPE)) {
  12. connection.setRequestProperty(
  13. HttpHeaderParser.HEADER_CONTENT_TYPE, request.getBodyContentType());
  14. }
  15. DataOutputStream out = new DataOutputStream(connection.getOutputStream());
  16. out.write(body);
  17. out.close();
  18. }

Request里面的getBody()的默认实现就是获取getParams(),获取到参数,然后构建A=xxx这种请求体

然后,获取connection.getResponseCode(),如果是-1,直接抛异常,这个-1主要是connection发生了一些未知错误。
接着,hasResponseBody()来判断Response是否包含数据,根据是否有数据来构建不同的HttpResponse。

总结

HurlStack,就是通过HttpUrlConnection把Request转换为HttpResponse。
输入:Request
输出:HttpResponse

BasicNetwork

  1. public interface Network {
  2. NetworkResponse performRequest(Request<?> request) throws VolleyError;
  3. }
  4. public class BasicNetwork implements Network {}

BasicNetwork 这个类我觉得,它就是一个用来转换的类,它的作用,说白就是就是输入XXXRequest,输出NetworkResponse:

在 BasicNetwork 中有一个 byte[] 复用池。目的在于不要频繁创建生命周期比较短的 byte[] 对象会使堆频繁分配位置以及在 Android 垃圾回收导致的延时。

performRequest
  1. Map<String, String> additionalRequestHeaders =
  2. getCacheHeaders(request.getCacheEntry());
  3. httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);

加入缓存信息的header,这个就是HrulStack里面说的缓存header,当时还说了就两个值If-None-Match和If-Modified-Since,为什么呢?我们来看:

  1. private Map<String, String> getCacheHeaders(Cache.Entry entry) {
  2. // If there's no cache entry, we're done.
  3. if (entry == null) {
  4. return Collections.emptyMap();
  5. }
  6. Map<String, String> headers = new HashMap<>();
  7. if (entry.etag != null) {
  8. headers.put("If-None-Match", entry.etag);
  9. }
  10. if (entry.lastModified > 0) {
  11. headers.put(
  12. "If-Modified-Since", HttpHeaderParser.formatEpochAsRfc1123(entry.lastModified));
  13. }
  14. return headers;
  15. }

我们可以看到,这个就是根据Cache.Enrty里面的值得到对应的header。当然这个Cache.Enrty可能为null,当从CacheDispatcher过来的时候不为null,当从NetworkDispatcher过来的时候为null。

  1. if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
  2. Entry entry = request.getCacheEntry();
  3. if (entry == null) {
  4. return new NetworkResponse(
  5. HttpURLConnection.HTTP_NOT_MODIFIED,
  6. /* data= */ null,
  7. /* notModified= */ true,
  8. SystemClock.elapsedRealtime() - requestStart,
  9. responseHeaders);
  10. }
  11. // Combine cached and response headers so the response will be complete.
  12. List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);
  13. return new NetworkResponse(
  14. HttpURLConnection.HTTP_NOT_MODIFIED,
  15. entry.data,
  16. /* notModified= */ true,
  17. SystemClock.elapsedRealtime() - requestStart,
  18. combinedHeaders);
  19. }

http返回了304,然后根据Entry,生成NetworkResponse,并返回。
其中combineHeaders()会把cache里面的header和返回的header进行合并。

  1. InputStream inputStream = httpResponse.getContent();
  2. if (inputStream != null) {
  3. responseContents =
  4. inputStreamToBytes(inputStream, httpResponse.getContentLength());
  5. } else {
  6. // Add 0 byte response as a way of honestly representing a
  7. // no-content request.
  8. responseContents = new byte[0];
  9. }

这个inputStream就是HrulStack里面的UrlConnectionInputStream,获取Response里面的数据。

  1. long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
  2. logSlowRequests(requestLifetime, request, responseContents, statusCode);
  3. if (statusCode < 200 || statusCode > 299) {
  4. throw new IOException();
  5. }
  6. return new NetworkResponse(
  7. statusCode,
  8. responseContents,
  9. /* notModified= */ false,
  10. SystemClock.elapsedRealtime() - requestStart,
  11. responseHeaders);

封装NetworkResponse,并返回。

总结

实现了Network,通过这个接口我们就能看出来它的作用:
输入:XXXRequest
输出:NetworkResponse

RequestQueue

咱们通过Volley类创建了RequestQueue,其实我们一般使用Volley
的api,大多数都是通过RequestQueue来调用的:

  1. MyApplication.getHttpQueue().add(stringRequest);
  2. MyApplication.getHttpQueues.cancelAll("stringGet");
  3. MyApplication.getHttpQueues.cancelAll(RequestFilter);

具体的可以参见前面的使用章节

首先来看RequestQueue的构造函数,我们从Volley创建的时候,使用的是:

  1. public RequestQueue(Cache cache, Network network) {
  2. this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
  3. }

其中,DEFAULT_NETWORK_THREAD_POOL_SIZE = 4
最终会调用的构造函数是:

  1. public RequestQueue(
  2. Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
  3. mCache = cache;
  4. mNetwork = network;
  5. mDispatchers = new NetworkDispatcher[threadPoolSize];
  6. mDelivery = delivery;
  7. }
  8. public RequestQueue(Cache cache, Network network, int threadPoolSize) {
  9. this(
  10. cache,
  11. network,
  12. threadPoolSize,
  13. new ExecutorDelivery(new Handler(Looper.getMainLooper())));
  14. }

先记下来这两个类,我们先看RequestQueue的start方法

  1. public void start() {
  2. stop();
  3. mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
  4. mCacheDispatcher.start();
  5. for (int i = 0; i < mDispatchers.length; i++) {
  6. NetworkDispatcher networkDispatcher =
  7. new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
  8. mDispatchers[i] = networkDispatcher;
  9. networkDispatcher.start();
  10. }
  11. }

首先先调用了stop(),我们来看下

  1. public void stop() {
  2. if (mCacheDispatcher != null) {
  3. mCacheDispatcher.quit();
  4. }
  5. for (final NetworkDispatcher mDispatcher : mDispatchers) {
  6. if (mDispatcher != null) {
  7. mDispatcher.quit();
  8. }
  9. }
  10. }

调用了CacheDispatcher和NetworkDispatcher的quit(),这个quit方法,两个类的实现是一致的:

  1. public void quit() {
  2. mQuit = true;
  3. interrupt();
  4. }
  5. @Override
  6. public void run() {
  7. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  8. while (true) {
  9. try {
  10. processRequest();
  11. } catch (InterruptedException e) {
  12. // We may have been interrupted because it was time to quit.
  13. if (mQuit) {
  14. Thread.currentThread().interrupt();
  15. return;
  16. }
  17. VolleyLog.e(
  18. "Ignoring spurious interrupt of NetworkDispatcher thread; "
  19. + "use quit() to terminate it");
  20. }
  21. }
  22. }

其实CacheDispatcher和NetworkDispatcher都是继承的Thread,并且这两个里面都是用到BlockingQueue的,所以上面的退出就很好理解了。

好了,言归正传,继续说start()

  1. public void start() {
  2. stop();
  3. mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
  4. mCacheDispatcher.start();
  5. for (int i = 0; i < mDispatchers.length; i++) {
  6. NetworkDispatcher networkDispatcher =
  7. new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
  8. mDispatchers[i] = networkDispatcher;
  9. networkDispatcher.start();
  10. }
  11. }

上面说过了,CacheDispatcher 和 NetworkDispatcher 都是继承自 Thread ,所以这里会启动5个线程,等待网络请求的到来,其中 CacheDispatcher 是缓存线程,NetworkDispatcher 是网络请求线程。

接下来,看下add方法:

  1. public <T> Request<T> add(Request<T> request) {
  2. // Tag the request as belonging to this queue and add it to the set of current requests.
  3. request.setRequestQueue(this);
  4. synchronized (mCurrentRequests) {
  5. mCurrentRequests.add(request);
  6. }
  7. // Process requests in the order they are added.
  8. request.setSequence(getSequenceNumber());
  9. request.addMarker("add-to-queue");
  10. // If the request is uncacheable, skip the cache queue and go straight to the network.
  11. if (!request.shouldCache()) {
  12. mNetworkQueue.add(request);
  13. return request;
  14. }
  15. mCacheQueue.add(request);
  16. return request;
  17. }

我们在看下cancel()

  1. public void cancelAll(RequestFilter filter) {
  2. synchronized (mCurrentRequests) {
  3. for (Request<?> request : mCurrentRequests) {
  4. if (filter.apply(request)) {
  5. request.cancel();
  6. }
  7. }
  8. }
  9. }
  10. public void cancelAll(final Object tag) {
  11. if (tag == null) {
  12. throw new IllegalArgumentException("Cannot cancelAll with a null tag");
  13. }
  14. cancelAll(
  15. new RequestFilter() {
  16. @Override
  17. public boolean apply(Request<?> request) {
  18. return request.getTag() == tag;
  19. }
  20. });
  21. }

其实很简单,就是循环遍历,当前的所有的Request,调用对应的cancel()

我们先来看下mCurrentRequests是怎么维护的:

然后来看下Request的cancel():

  1. public void cancel() {
  2. synchronized (mLock) {
  3. mCanceled = true;
  4. mErrorListener = null;
  5. }
  6. }

非常简单,就是改变了mCanceled的值,所以肯定是在请求网络的时候,会来判断这个值,来决定是否请求网络或者返回结果后,是否通知上层。

NetworkDispatcher

  1. @Override
  2. public void run() {
  3. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  4. while (true) {
  5. try {
  6. processRequest();
  7. } catch (InterruptedException e) {
  8. // We may have been interrupted because it was time to quit.
  9. if (mQuit) {
  10. Thread.currentThread().interrupt();
  11. return;
  12. }
  13. VolleyLog.e(
  14. "Ignoring spurious interrupt of NetworkDispatcher thread; "
  15. + "use quit() to terminate it");
  16. }
  17. }
  18. }

主要方法是processRequest(),会被不断调用

  1. private void processRequest() throws InterruptedException {
  2. // Take a request from the queue.
  3. Request<?> request = mQueue.take();
  4. processRequest(request);
  5. }

从队列里面取一个值,如果没有就阻塞等待,如果有,就往下走,调用processRequest(request),这个方法是最重要的方法了,我们分段来看:

  1. if (request.isCanceled()) {
  2. request.finish("network-discard-cancelled");
  3. request.notifyListenerResponseNotUsable();
  4. return;
  5. }

发起请求前,如果这个Request已经被cancel,那么就通知,然后return,继续下一次循环

  1. addTrafficStatsTag(request);
  2. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  3. private void addTrafficStatsTag(Request<?> request) {
  4. // Tag the request (if API >= 14)
  5. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  6. TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
  7. }
  8. }

流量监控,给这个请求设置tag,默认是url的host的hashcode

  1. NetworkResponse networkResponse = mNetwork.performRequest(request);
  2. request.addMarker("network-http-complete");

调用BasicNetwork来完成网络请求,并得到NetworkResponse返回

  1. if (networkResponse.notModified && request.hasHadResponseDelivered()) {
  2. request.finish("not-modified");
  3. request.notifyListenerResponseNotUsable();
  4. return;
  5. }

如果服务器返回 304,而且我们已经分发过该 Request 的结果,那就不用进行第二次分发了。
304代表服务器上的结果跟上次访问的结果是一样的,也就是说数据没有变化。

  1. // Parse the response here on the worker thread.
  2. Response<?> response = request.parseNetworkResponse(networkResponse);
  3. request.addMarker("network-parse-complete");

调用XxxReqeust的parseNetworkResponse方法,得到Xxx的数据类型,把NetworkResponse,转换为Response

  1. // Write to cache if applicable.
  2. if (request.shouldCache() && response.cacheEntry != null) {
  3. mCache.put(request.getCacheKey(), response.cacheEntry);
  4. request.addMarker("network-cache-written");
  5. }

如果该Request允许缓存,就调用DiskBasedCache进行缓存处理。response.cacheEntry 如果为null,就证明这个请求返回的response里面设置来不需要缓存,具体参考 HttpHeaderParser

  1. // Post the response back.
  2. request.markDelivered();
  3. mDelivery.postResponse(request, response);
  4. 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()

CacheDispatcher

和NetworkDispatcher基本类似,我们直奔processRequest(final Request request)方法

  1. if (request.isCanceled()) {
  2. request.finish("cache-discard-canceled");
  3. return;
  4. }

如果请求已经取消了,我们直接结束该请求

  1. Cache.Entry entry = mCache.get(request.getCacheKey());
  2. if (entry == null) {
  3. request.addMarker("cache-miss");
  4. // Cache miss; send off to the network dispatcher.
  5. if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
  6. mNetworkQueue.put(request);
  7. }
  8. return;
  9. }

从 Cache 中取出包含请求缓存数据的 Entry,如果Entry为null,那么就说明没有cache,然后判断是否之前已经有过同样的Reqeust,如果没有就添加进mNetworkQueue,等待进行网络请求

  1. if (entry.isExpired()) {
  2. request.addMarker("cache-hit-expired");
  3. request.setCacheEntry(entry);
  4. if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
  5. mNetworkQueue.put(request);
  6. }
  7. return;
  8. }

如果cache已经过期,那么就判断是否之前已经有过同样的Reqeust,如果没有就添加进mNetworkQueue,等待进行网络请求

  1. request.addMarker("cache-hit");
  2. Response<?> response =
  3. request.parseNetworkResponse(
  4. new NetworkResponse(entry.data, entry.responseHeaders));
  5. request.addMarker("cache-hit-parsed");

走到这里,我们就得到了一个cache,然后使用cache构建一个NetworkResponse,并解析转换为Response

  1. if (!entry.refreshNeeded()) {
  2. // Completely unexpired cache hit. Just deliver the response.
  3. mDelivery.postResponse(request, response);
  4. } else {
  5. request.addMarker("cache-hit-refresh-needed");
  6. request.setCacheEntry(entry);
  7. response.intermediate = true;
  8. if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)){
  9. mDelivery.postResponse(
  10. request,
  11. response,
  12. new Runnable() {
  13. @Override
  14. public void run() {
  15. try {
  16. mNetworkQueue.put(request);
  17. } catch (InterruptedException e) {
  18. // Restore the interrupted status
  19. Thread.currentThread().interrupt();
  20. }
  21. }
  22. });
  23. } else {
  24. mDelivery.postResponse(request, response);
  25. }

好了,到此为止,CacheDispatcher已经分析晚了,我们可以总结一下:

  • 如果cache可用,就直接把缓存封装成response,ResponseDelivery进行分发
    • 如果cache需要刷新,就在ResponseDelivery分发完成之后,把当前reqeust放入到network队列中去(当然会通过等待队列进行判断)
  • 如果cache不可用,那么就把当前reqeust放入到network队列中去(当然会通过等待队列进行判断)
WaitingRequestManager

当缓存不存在、过期、需要刷新的时候,都会调用WaitingRequestManager.maybeAddToWaitingRequests(),也就是说需要将request放到NetworkDispatcher的队列中的时候,都会调用这个方法。

那这个类有什么作用呢?我们理一理

  1. // cacheKey和request的对应关系
  2. Map<String, List<Request<?>>> mWaitingRequests = new HashMap<>();
  3. void onResponseReceived(Request<?> request, Response<?> response)
  4. void onNoUsableResponseReceived(Request<?> request)
  5. boolean maybeAddToWaitingRequests(Request<?> request)

ExecutorDelivery

我们前面说了很多次ResponseDelivery分发,这个接口就是用来分发response的,他的唯一实现类就是ExecutorDelivery:

  1. public interface ResponseDelivery {
  2. void postResponse(Request<?> request, Response<?> response);
  3. void postResponse(Request<?> request, Response<?> response, Runnable runnable);
  4. void postError(Request<?> request, VolleyError error);
  5. }

ExecutorDelivery的代码也比较简单:

  1. public class ExecutorDelivery implements ResponseDelivery {
  2. private final Executor mResponsePoster;
  3. public ExecutorDelivery(final Handler handler) {
  4. mResponsePoster =
  5. new Executor() {
  6. @Override
  7. public void execute(Runnable command) {
  8. handler.post(command);
  9. }
  10. };
  11. }
  12. // ....
  13. @Override
  14. public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
  15. request.markDelivered();
  16. request.addMarker("post-response");
  17. mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
  18. }
  19. @Override
  20. public void postError(Request<?> request, VolleyError error) {
  21. request.addMarker("post-error");
  22. Response<?> response = Response.error(error);
  23. mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
  24. }
  25. private static class ResponseDeliveryRunnable implements Runnable {
  26. private final Request mRequest;
  27. private final Response mResponse;
  28. private final Runnable mRunnable;
  29. public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
  30. mRequest = request;
  31. mResponse = response;
  32. mRunnable = runnable;
  33. }
  34. @SuppressWarnings("unchecked")
  35. @Override
  36. public void run() {
  37. if (mRequest.isCanceled()) {
  38. mRequest.finish("canceled-at-delivery");
  39. return;
  40. }
  41. // Deliver a normal response or error, depending.
  42. if (mResponse.isSuccess()) {
  43. mRequest.deliverResponse(mResponse.result);
  44. } else {
  45. mRequest.deliverError(mResponse.error);
  46. }
  47. // If this is an intermediate response, add a marker, otherwise we're done
  48. // and the request can be finished.
  49. if (mResponse.intermediate) {
  50. mRequest.addMarker("intermediate-response");
  51. } else {
  52. mRequest.finish("done");
  53. }
  54. // If we have been provided a post-delivery runnable, run it.
  55. if (mRunnable != null) {
  56. mRunnable.run();
  57. }
  58. }
  59. }
  60. }

构造函数根据handler创建了一个Executor:

  1. mResponsePoster =
  2. new Executor() {
  3. @Override
  4. public void execute(Runnable command) {
  5. handler.post(command);
  6. }
  7. };

不太明白这个Executor有什么作用,个人感觉直接用Handler是一模一样的。
这个类里面的所有操作都是在主线程进行的。
我们来看下这个类是在RequestQueue里面创建的:

  1. public RequestQueue(Cache cache, Network network, int threadPoolSize) {
  2. this(cache,
  3. network,
  4. threadPoolSize,
  5. new ExecutorDelivery(new Handler(Looper.getMainLooper())));
  6. }

主要看下那个内部类Runnable:

  1. if (mRequest.isCanceled()) {
  2. mRequest.finish("canceled-at-delivery");
  3. return;
  4. }
  5. // Deliver a normal response or error, depending.
  6. if (mResponse.isSuccess()) {
  7. mRequest.deliverResponse(mResponse.result);
  8. } else {
  9. mRequest.deliverError(mResponse.error);
  10. }
  11. // If this is an intermediate response, add a marker, otherwise we're done
  12. // and the request can be finished.
  13. if (mResponse.intermediate) {
  14. mRequest.addMarker("intermediate-response");
  15. } else {
  16. mRequest.finish("done");
  17. }
  18. // If we have been provided a post-delivery runnable, run it.
  19. if (mRunnable != null) {
  20. mRunnable.run();
  21. }

我们可以看到是调用Request来处理Response和Error的,我们看下Request里面:

  1. protected abstract void deliverResponse(T response);
  2. public void deliverError(VolleyError error) {
  3. Response.ErrorListener listener;
  4. synchronized (mLock) {
  5. listener = mErrorListener;
  6. }
  7. if (listener != null) {
  8. listener.onErrorResponse(error);
  9. }
  10. }

deliverResponse()是一个抽象方法,而deliverError的处理很简单,就是调用ErrorListener。
那么我们来看下StringReqeust的实现:

  1. protected void deliverResponse(String response) {
  2. Response.Listener<String> listener;
  3. synchronized (mLock) {
  4. listener = mListener;
  5. }
  6. if (listener != null) {
  7. listener.onResponse(response);
  8. }
  9. }

也是直接调用Listener,返回结果。

我们会发现

  • 我们处理Response的是在Request,调用的parseNetworkResponse()
  • 我们分发结果最终也是在Request,调用的传进来的两个Listener

所以我们现在拐回去看下,Volley的使用:

  1. StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
  2. @Override
  3. public void onResponse(String response) {
  4. //这里可以更新UI
  5. }
  6. }, new Response.ErrorListener() {
  7. @Override
  8. public void onErrorResponse(VolleyError error) {
  9. }
  10. });

这两个listener就是最后处理结果的,而如果我们想自定义一个Request,那么就需要实现parseNetworkResponse(),来解析结果。

HttpHeaderParser

我们来看下HttpHeaderParser的逻辑:

  1. 有cache-control
    1.1. softTtl = now + maxAge * 1000,当前时间 + 最大过期时间的毫秒值
    1.2. 是否存在must-revalidate & proxy-revalidate,也就是强制过期必须请求
    1.3. 如果存在,ttl = softTtl
    1.4. 如果不存在,ttl = softTtl + 缓存过期后还可以继续使用多久的毫秒值
  2. 没有cache-control,并且存在Date和Expires,且Expires > Date
    2.1. softTtl = now + (Expires - Date)
    2.2. ttl = softTtl
  3. 都不符合,取默认值,都为0

另一个角度

我们可以从之前说的那张图,来换个角度理解Volley

Volley缓存

http缓存

彻底理解浏览器的缓存机制

强制缓存

图中的浏览器缓存,可以换成是Volley缓存即可。

强制缓存相关的header:

  1. Expires: Wed, 21 Oct 2015 07:28:00 GMT
  2. Cache-Control: max-age=600

可以看出来:

Cache-Control优先级比Expires高

协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

图中的浏览器缓存,可以换成是Volley缓存即可。

协商缓存相关的header:

  1. // response
  2. last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
  3. etag: W/"175737-152583456000"
  4. // request
  5. if-modified-since: Wed, 21 Oct 2015 07:28:00 GMT
  6. 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,继续使用缓存,主要过程如下:

本地缓存

Android Volley 源码解析(二),探究缓存机制

Cache

Volley 提供了一个 Cache 作为缓存的接口,封装了缓存的实体 Entry,以及一些常规的增删查操作。

  1. public interface Cache {
  2. Entry get(String key);
  3. void put(String key, Entry entry);
  4. void initialize();
  5. /**
  6. * 使缓存中的 Entry 失效
  7. */
  8. void invalidate(String key, boolean fullExpire);
  9. void remove(String key);
  10. void clear();
  11. /**
  12. * 用户缓存的实体
  13. */
  14. class Entry {
  15. public byte[] data;
  16. public String etag;
  17. public long serverDate;
  18. public long lastModified;
  19. public long ttl;
  20. public long softTtl;
  21. public Map<String, String> responseHeaders = Collections.emptyMap();
  22. public List<Header> allResponseHeaders;
  23. /** 判断 Entry 是否过期. */
  24. public boolean isExpired() {
  25. return this.ttl < System.currentTimeMillis();
  26. }
  27. /** 判断 Entry 是否需要刷新. */
  28. public boolean refreshNeeded() {
  29. return this.softTtl < System.currentTimeMillis();
  30. }
  31. }
  32. }

Entry 作为缓存的实体,主要有三个部分:

DiskBaseCache

Cache 的具体实现类 DiskBaseCache,我们先来看一下它的内部类CacheHeader

  1. static class CacheHeader {
  2. long size;
  3. final String key;
  4. final String etag;
  5. final long serverDate;
  6. final long lastModified;
  7. final long ttl;
  8. final long softTtl;
  9. final List<Header> allResponseHeaders;
  10. private CacheHeader(String key, String etag, long serverDate, long lastModified, long ttl,
  11. long softTtl, List<Header> allResponseHeaders) {
  12. this.key = key;
  13. this.etag = ("".equals(etag)) ? null : etag;
  14. this.serverDate = serverDate;
  15. this.lastModified = lastModified;
  16. this.ttl = ttl;
  17. this.softTtl = softTtl;
  18. this.allResponseHeaders = allResponseHeaders;
  19. }
  20. CacheHeader(String key, Entry entry) {
  21. this(key, entry.etag, entry.serverDate, entry.lastModified, entry.ttl, entry.softTtl,
  22. getAllResponseHeaders(entry));
  23. size = entry.data.length;
  24. }
  25. /**
  26. * CacheHeader转换为Entry
  27. **/
  28. Entry toCacheEntry(byte[] data) {
  29. Entry e = new Entry();
  30. e.data = data;
  31. e.etag = etag;
  32. e.serverDate = serverDate;
  33. e.lastModified = lastModified;
  34. e.ttl = ttl;
  35. e.softTtl = softTtl;
  36. e.responseHeaders = HttpHeaderParser.toHeaderMap(allResponseHeaders);
  37. e.allResponseHeaders = Collections.unmodifiableList(allResponseHeaders);
  38. return e;
  39. }
  40. }

我们可以发现CacheHeader跟 Cache 的 Entry 非常像,是不是会觉得好像有点多余呢?我们具体 来看:
CacheHeader 和 Entry 最大的区别,其实就是是byte[] data,data 代表网络响应的元数据,是返回的内容中最占地方的东西,所以 DiskBaseCache 重新抽象了一个不包含 data 的 CacheHeader,并将其缓存到内存中,而data 部分便存储在磁盘缓存中,这样就能最大程度的利用有限的内存空间,避免OOM了。

  1. BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(file));
  2. CacheHeader e = new CacheHeader(key, entry);
  3. boolean success = e.writeHeader(fos);
  4. // 将 entry.data 写入磁盘中
  5. fos.write(entry.data);
  6. fos.close();
  7. // 将 Cache 缓存到内存中
  8. putEntry(key, e);

也就是说,内存缓存里面是不包含data的CacheHeader,而如果需要Entry,会从File里面读取出data数据。

初始化缓存
  1. public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
  2. mRootDirectory = rootDirectory;
  3. mMaxCacheSizeInBytes = maxCacheSizeInBytes;
  4. }
  5. public DiskBasedCache(File rootDirectory) {
  6. this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
  7. }
  8. @Override
  9. public synchronized void initialize() {
  10. // 如果 mRootDirectroy 不存在,则进行创建
  11. if (!mRootDirectory.exists()) {
  12. if (!mRootDirectory.mkdirs()) {
  13. VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
  14. }
  15. return;
  16. }
  17. File[] files = mRootDirectory.listFiles();
  18. if (files == null) {
  19. return;
  20. }
  21. // 遍历 mRootDirectory 中的所有文件
  22. for (File file : files) {
  23. try {
  24. long entrySize = file.length();
  25. CountingInputStream cis =
  26. new CountingInputStream(
  27. new BufferedInputStream(createInputStream(file)), entrySize);
  28. try {
  29. // 将对应的文件的CacheHeader缓存到内存中,注意不包含data
  30. CacheHeader entry = CacheHeader.readHeader(cis);
  31. entry.size = entrySize;
  32. putEntry(entry.key, entry);
  33. } finally {
  34. cis.close();
  35. }
  36. } catch (IOException e) {
  37. file.delete();
  38. }
  39. }
  40. }

如果mRootDirectory下面有文件,就将所有文件读进内存缓存里。
这里说一下这个read,这个是根据write的顺序进行读的,特别的String类型:

put()
  1. public synchronized void put(String key, Entry entry) {
  2. // 添加缓存之前,先判断是否需要清理一下
  3. pruneIfNeeded(entry.data.length);
  4. // 根据key生成唯一名字的文件
  5. File file = getFileForKey(key);
  6. try {
  7. BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(file));
  8. CacheHeader e = new CacheHeader(key, entry);
  9. // 保存除了data之外的所有数据
  10. boolean success = e.writeHeader(fos);
  11. if (!success) {
  12. fos.close();
  13. VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
  14. throw new IOException();
  15. }
  16. // 接着保存data数据
  17. fos.write(entry.data);
  18. fos.close();
  19. // CacheHeader保存到内存中
  20. putEntry(key, e);
  21. return;
  22. } catch (IOException e) {
  23. }
  24. // 缓存失败,就删除文件
  25. boolean deleted = file.delete();
  26. if (!deleted) {
  27. VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
  28. }
  29. }
  30. /**
  31. * 是否需要清理磁盘空间
  32. **/
  33. private void pruneIfNeeded(int neededSpace) {
  34. // 如果还够用,就直接 return.
  35. if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
  36. return;
  37. }
  38. if (VolleyLog.DEBUG) {
  39. VolleyLog.v("Pruning old cache entries.");
  40. }
  41. long before = mTotalSize;
  42. int prunedFiles = 0;
  43. long startTime = SystemClock.elapsedRealtime();
  44. Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
  45. // 遍历所有的缓存,开始进行删除
  46. while (iterator.hasNext()) {
  47. Map.Entry<String, CacheHeader> entry = iterator.next();
  48. CacheHeader e = entry.getValue();
  49. // 删除磁盘文件
  50. boolean deleted = getFileForKey(e.key).delete();
  51. if (deleted) {
  52. mTotalSize -= e.size;
  53. } else {
  54. VolleyLog.d(
  55. "Could not delete cache entry for key=%s, filename=%s", e.key, getFilenameForKey(e.key));
  56. }
  57. iterator.remove();
  58. prunedFiles++;
  59. // 如果删除之后,空间已经够用了,就停止循环
  60. if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
  61. break;
  62. }
  63. }
  64. if (VolleyLog.DEBUG) {
  65. VolleyLog.v(
  66. "pruned %d files, %d bytes, %d ms", prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
  67. }
  68. }

需要注意的就是这个mEntries是LinkedHashMap,采用LRU算法。
另外,关于唯一key的生成是这样的:

  1. private String getFilenameForKey(String key) {
  2. int firstHalfLength = key.length() / 2;
  3. String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
  4. localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
  5. return localFilename;
  6. }

将网络请求的 Url 分成两半,然后将这两部分的 hashCode 拼接成缓存 key。Volley 之所以要这样做,主要是为了尽量避免 hashCode 重复造成的文件名重复,求两次 hashCode 都与另外一个 Url 相同的概率比只求一次要小很多。

get()
  1. public synchronized Entry get(String key) {
  2. CacheHeader entry = mEntries.get(key);
  3. if (entry == null) {
  4. return null;
  5. }
  6. File file = getFileForKey(key);
  7. try {
  8. CountingInputStream cis =
  9. new CountingInputStream(
  10. new BufferedInputStream(createInputStream(file)), file.length());
  11. try {
  12. // 为啥再次读一次呢?
  13. CacheHeader entryOnDisk = CacheHeader.readHeader(cis);
  14. if (!TextUtils.equals(key, entryOnDisk.key)) {
  15. VolleyLog.d(
  16. "%s: key=%s, found=%s", file.getAbsolutePath(), key, entryOnDisk.key);
  17. removeEntry(key);
  18. return null;
  19. }
  20. // 读去data数据
  21. byte[] data = streamToBytes(cis, cis.bytesRemaining());
  22. // CacheHeader转换为Entry
  23. return entry.toCacheEntry(data);
  24. } finally {
  25. cis.close();
  26. }
  27. } catch (IOException e) {
  28. VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
  29. remove(key);
  30. return null;
  31. }
  32. }

为啥再次读一次呢?
为了能正确读到data。因为这个File的前面是其他信息,需要把这些信息读完,才能够读到data。

使用
创建

我们是在Volley#newRequestQueue()创建了DiskBasedCache:

  1. private static RequestQueue newRequestQueue(Context context, Network network) {
  2. File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
  3. RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
  4. queue.start();
  5. return queue;
  6. }

默认的内存缓存空间是 5M

initialize()
  1. public class CacheDispatcher extends Thread {
  2. // balabala
  3. public void run() {
  4. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  5. // 这里!!!!
  6. mCache.initialize();
  7. while (true) {
  8. try {
  9. processRequest();
  10. } catch (InterruptedException e) {
  11. if (mQuit) {
  12. Thread.currentThread().interrupt();
  13. return;
  14. }
  15. }
  16. }
  17. }
  18. // balabala
  19. }

initialize() 是在 CacheDispatcher 中的 run 方法进行调用的,因为初始化的时候会有很多io操作,从磁盘读取缓存到内存,所以在子线程里面执行

put()
  1. public class NetworkDispatcher extends Thread {
  2. // balabala
  3. void processRequest(Request<?> request) {
  4. // balabala
  5. if (request.shouldCache() && response.cacheEntry != null) {
  6. mCache.put(request.getCacheKey(), response.cacheEntry);
  7. request.addMarker("network-cache-written");
  8. }
  9. // balabala
  10. }
  11. // balabala
  12. }

put() 方法是在 NetworkDispatcher 中进行调用的,网络请求成功之后,就会根据情况来保存缓存。response.cacheEntry是在HttpHeaderParser解析的,在Reponse里面封装的。

get()
  1. public class CacheDispatcher extends Thread {
  2. // balabala
  3. void processRequest(final Request<?> request) throws InterruptedException {
  4. // balabala
  5. Cache.Entry entry = mCache.get(request.getCacheKey());
  6. // balabala
  7. }
  8. // balabala
  9. }

get()方法也是在CacheDispatcher的 run() 方法中调用。当有请求到来时,便先根据请求的 Url 拿出对应的缓存在内存中的 Entry,然后对 Entry 进行一些判断和处理,最后将其构建成 Response 回调出去。

总结

Volley封装

https://www.cnblogs.com/dasusu/p/9837950.html

扩展

http://lijiankun24.com/Volley%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E5%8F%8A%E5%AF%B9Volley%E7%9A%84%E6%89%A9%E5%B1%953/

总结

参考

https://blog.csdn.net/chunqiuwei/article/details/78003929
https://www.cnblogs.com/dasusu/p/9837950.html

拾遗

壹 错误处理

贰 byte[]复用池

在 BasicNetwork 中有一个 byte[] 复用池。目的在于不要频繁创建生命周期比较短的 byte[] 对象会使堆频繁分配位置以及在 Android 垃圾回收导致的延时。

叁 UrlConnectionInputStream

  1. static class UrlConnectionInputStream extends FilterInputStream {
  2. private final HttpURLConnection mConnection;
  3. UrlConnectionInputStream(HttpURLConnection connection) {
  4. super(inputStreamFromConnection(connection));
  5. mConnection = connection;
  6. }
  7. @Override
  8. public void close() throws IOException {
  9. super.close();
  10. mConnection.disconnect();
  11. }
  12. }
  13. /**
  14. * Initializes an {@link InputStream} from the given {@link HttpURLConnection}.
  15. *
  16. * @param connection
  17. * @return an HttpEntity populated with data from <code>connection</code>.
  18. */
  19. private static InputStream inputStreamFromConnection(HttpURLConnection connection) {
  20. InputStream inputStream;
  21. try {
  22. inputStream = connection.getInputStream();
  23. } catch (IOException ioe) {
  24. inputStream = connection.getErrorStream();
  25. }
  26. return inputStream;
  27. }

它是HttpURLConnection的InputStream的装饰类,用于关闭连接。

肆 单元测试

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注