[关闭]
@flyouting 2014-07-12T14:43:59.000000Z 字数 15500 阅读 4553

Volley源码学习笔记一

Android volley


Volley框架的官方架构图

此处输入图片的描述

从图中大概可以看出,框架中包含三种线程,主线程,内存线程,网路线程。网络线程可以是多个,主线程负责将请求按优先权顺序添加进任务队列,内存线程查看缓存中是否有此请求,有的话从缓存中获取返回数据回馈给主线程,否则将请求交给网络线程,网络线程多个任务线程(NetworkDispatcher)组成,这些任务线程同时启动,不停的从任务队列中获取待执行的任务,执行完毕后把返回结果回馈给主线程。

Volley框架HTTP请求流程图

此处输入图片的描述

从图中可以简单了解到,各类请求添加进请求队列,然后分发给网络线程(RequestDispatcher),通过HurlStack或者HttpClientStack执行网络请求,得到NetworkResponse返回,如果是错误,可以retry一次,否则直接返回给主线程错误信息,如果返回数据正确,就会解析成Response<T> 通过ResponseDelivery返回给主线程。

代码分析

首先是查看创建请求队列RequestQueue

  1. /**
  2. * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
  3. *
  4. * @param context A {@link Context} to use for creating the cache dir.
  5. * @param stack An {@link HttpStack} to use for the network, or null for default.
  6. * @return A started {@link RequestQueue} instance.
  7. */
  8. public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
  9. //这里创建缓存目录
  10. File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
  11. //这里创建默认的useragent
  12. String userAgent = "volley/0";
  13. try {
  14. String packageName = context.getPackageName();
  15. PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
  16. userAgent = packageName + "/" + info.versionCode;
  17. } catch (NameNotFoundException e) {
  18. }
  19. //可以传入一个自定义的HttpStack,比如OkHttpClient
  20. if (stack == null) {
  21. if (Build.VERSION.SDK_INT >= 9) {
  22. stack = new HurlStack();
  23. } else {
  24. // Prior to Gingerbread, HttpUrlConnection was unreliable.
  25. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
  26. stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
  27. }
  28. }
  29. Network network = new BasicNetwork(stack);
  30. RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
  31. queue.start();
  32. return queue;
  33. }

先看下HttpStack,这里的一个参数:

  1. /**
  2. * An HTTP stack abstraction.
  3. */
  4. public interface HttpStack {
  5. /**
  6. * Performs an HTTP request with the given parameters.
  7. *
  8. * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
  9. * and the Content-Type header is set to request.getPostBodyContentType().</p>
  10. *
  11. * @param request the request to perform
  12. * @param additionalHeaders additional headers to be sent together with
  13. * {@link Request#getHeaders()}
  14. * @return the HTTP response
  15. */
  16. public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
  17. throws IOException, AuthFailureError;
  18. }

根据描述可以看出这是一个抽象的Http请求客户端,为了兼容不同的网络请求。定义了一个网络执行方法,返回的是HttpResponse类型。即不管用何种方式执行网络请求,返回格式需要封装成HttpResponse。

代码中网络请求的实现由两种类型,一种是Java原生的HttpURLConnection实现(HurlStack),一种是Apache的HttpClient实现(HttpClientStack),Volley会在android2.3以前使用HttpClient实现,在android2.3及以后使用HttpURLConnection实现,至于原因,官方的解释是:在Eclair和Froyo上Apache HTTP client拥有更少的bug,更好的稳定性,在Gingerbread以及以后的版本中,HttpURLConnection是最好的选择,它简单的api以及轻量级非常适合Android。压缩和缓存机制降低了网路使用,提高了速度、节省了电量。

这里可以简单的看下HurlStackHttpClientStack对于网络执行方法performRequest的重写代码:

HurlStack:

  1. @Override
  2. public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
  3. throws IOException, AuthFailureError {
  4. String url = request.getUrl();
  5. HashMap<String, String> map = new HashMap<String, String>();
  6. map.putAll(request.getHeaders());
  7. map.putAll(additionalHeaders);
  8. if (mUrlRewriter != null) {
  9. String rewritten = mUrlRewriter.rewriteUrl(url);
  10. if (rewritten == null) {
  11. throw new IOException("URL blocked by rewriter: " + url);
  12. }
  13. url = rewritten;
  14. }
  15. URL parsedUrl = new URL(url);
  16. HttpURLConnection connection = openConnection(parsedUrl, request);
  17. for (String headerName : map.keySet()) {
  18. connection.addRequestProperty(headerName, map.get(headerName));
  19. }
  20. setConnectionParametersForRequest(connection, request);
  21. // Initialize HttpResponse with data from the HttpURLConnection.
  22. ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
  23. int responseCode = connection.getResponseCode();
  24. if (responseCode == -1) {
  25. // -1 is returned by getResponseCode() if the response code could not be retrieved.
  26. // Signal to the caller that something was wrong with the connection.
  27. throw new IOException("Could not retrieve response code from HttpUrlConnection.");
  28. }
  29. StatusLine responseStatus = new BasicStatusLine(protocolVersion,
  30. connection.getResponseCode(), connection.getResponseMessage());
  31. BasicHttpResponse response = new BasicHttpResponse(responseStatus);
  32. response.setEntity(entityFromConnection(connection));
  33. for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
  34. if (header.getKey() != null) {
  35. Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
  36. response.addHeader(h);
  37. }
  38. }
  39. return response;
  40. }

HttpClientStack:

  1. @Override
  2. public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
  3. throws IOException, AuthFailureError {
  4. HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
  5. addHeaders(httpRequest, additionalHeaders);
  6. addHeaders(httpRequest, request.getHeaders());
  7. onPrepareRequest(httpRequest);
  8. HttpParams httpParams = httpRequest.getParams();
  9. int timeoutMs = request.getTimeoutMs();
  10. // TODO: Reevaluate this connection timeout based on more wide-scale
  11. // data collection and possibly different for wifi vs. 3G.
  12. HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
  13. HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
  14. return mClient.execute(httpRequest);
  15. }

两种不同的方式,但是返回都封装成了HttpResponse

我们继续看执行网络请求的类Network和BasicNetwork
Network是一个接口,定义了一个执行网络请求的方法,BasicNetwork实现了这个接口,并根据不同的策略执行重连的操作。

  1. /**
  2. * An interface for performing requests.
  3. */
  4. public interface Network {
  5. /**
  6. * Performs the specified request.
  7. * @param request Request to process
  8. * @return A {@link NetworkResponse} with data and caching metadata; will never be null
  9. * @throws VolleyError on errors
  10. */
  11. public NetworkResponse performRequest(Request<?> request) throws VolleyError;
  12. }
  1. /**
  2. * A network performing Volley requests over an {@link HttpStack}.
  3. */
  4. public class BasicNetwork implements Network {
  5. protected static final boolean DEBUG = VolleyLog.DEBUG;
  6. private static int SLOW_REQUEST_THRESHOLD_MS = 3000;
  7. private static int DEFAULT_POOL_SIZE = 4096;
  8. 。。。
  9. @Override
  10. public NetworkResponse performRequest(Request<?> request) throws VolleyError {
  11. long requestStart = SystemClock.elapsedRealtime();
  12. while (true) {
  13. HttpResponse httpResponse = null;
  14. byte[] responseContents = null;
  15. Map<String, String> responseHeaders = new HashMap<String, String>();
  16. try {
  17. // 收集 headers.
  18. Map<String, String> headers = new HashMap<String, String>();
  19. addCacheHeaders(headers, request.getCacheEntry());
  20. //执行网络请求,获取返回数据
  21. httpResponse = mHttpStack.performRequest(request, headers);
  22. //获取状态码
  23. StatusLine statusLine = httpResponse.getStatusLine();
  24. int statusCode = statusLine.getStatusCode();
  25. //获取headers
  26. responseHeaders = convertHeaders(httpResponse.getAllHeaders());
  27. // 处理缓存验证.
  28. if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
  29. return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
  30. request.getCacheEntry() == null ? null : request.getCacheEntry().data,
  31. responseHeaders, true);
  32. }
  33. // 处理变动资源,如重定向
  34. if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
  35. String newUrl = responseHeaders.get("Location");
  36. request.setRedirectUrl(newUrl);
  37. }
  38. // 某些返回例如 204,不包含内容,需要检查
  39. if (httpResponse.getEntity() != null) {
  40. responseContents = entityToBytes(httpResponse.getEntity());
  41. } else {
  42. // 对于无内容返回的请求,添加一个0字节的返回内容
  43. responseContents = new byte[0];
  44. }
  45. // if the request is slow, log it.
  46. long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
  47. logSlowRequests(requestLifetime, request, responseContents, statusLine);
  48. if (statusCode < 200 || statusCode > 299) {
  49. throw new IOException();
  50. }
  51. return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
  52. } catch (SocketTimeoutException e) {
  53. attemptRetryOnException("socket", request, new TimeoutError());
  54. } catch (ConnectTimeoutException e) {
  55. attemptRetryOnException("connection", request, new TimeoutError());
  56. } catch (MalformedURLException e) {
  57. throw new RuntimeException("Bad URL " + request.getUrl(), e);
  58. } catch (IOException e) {
  59. int statusCode = 0;
  60. NetworkResponse networkResponse = null;
  61. if (httpResponse != null) {
  62. statusCode = httpResponse.getStatusLine().getStatusCode();
  63. } else {
  64. throw new NoConnectionError(e);
  65. }
  66. if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
  67. statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
  68. VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());
  69. } else {
  70. VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
  71. }
  72. if (responseContents != null) {
  73. networkResponse = new NetworkResponse(statusCode, responseContents,
  74. responseHeaders, false);
  75. if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
  76. statusCode == HttpStatus.SC_FORBIDDEN) {
  77. attemptRetryOnException("auth",
  78. request, new AuthFailureError(networkResponse));
  79. } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
  80. statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
  81. attemptRetryOnException("redirect",
  82. request, new AuthFailureError(networkResponse));
  83. } else {
  84. // TODO: Only throw ServerError for 5xx status codes.
  85. throw new ServerError(networkResponse);
  86. }
  87. } else {
  88. throw new NetworkError(networkResponse);
  89. }
  90. }
  91. }
  92. }
  93. 。。。
  94. }

Network对象生成后,会生成RequestQueue对象,看下RequestQueue的构造方法:

  1. /**
  2. * Creates the worker pool. Processing will not begin until {@link #start()} is called.
  3. *
  4. * @param cache 一个存储responses到磁盘的缓存
  5. * @param network 一个Network接口用以执行HTTP requests
  6. * @param threadPoolSize network dispatcher线程数量
  7. * @param delivery 一个ResponseDelivery接口用以分发 responses and errors
  8. */
  9. public RequestQueue(Cache cache, Network network, int threadPoolSize,
  10. ResponseDelivery delivery) {
  11. mCache = cache;
  12. mNetwork = network;
  13. mDispatchers = new NetworkDispatcher[threadPoolSize];
  14. mDelivery = delivery;
  15. }

默认的构造方法线程数是4,会生成一个默认的ResponseDelivery。然后RequestQueue会执行一个start()方法,启动一个cache线程和多个网络任务线程,等待执行任务。看源码:

  1. /**
  2. * Starts the dispatchers in this queue.
  3. */
  4. public void start() {
  5. stop(); // Make sure any currently running dispatchers are stopped.
  6. // Create the cache dispatcher and start it.
  7. mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
  8. mCacheDispatcher.start();
  9. // Create network dispatchers (and corresponding threads) up to the pool size.
  10. for (int i = 0; i < mDispatchers.length; i++) {
  11. NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
  12. mCache, mDelivery);
  13. mDispatchers[i] = networkDispatcher;
  14. networkDispatcher.start();
  15. }
  16. }

主体功能是创建一个内存分发器线程并启动,创建若干(默认是4)网络分发器线程并启动。分别来看下这两个类的代码,CacheDispatcher
构造函数:

  1. /**
  2. * Creates a new cache triage dispatcher thread. You must call {@link #start()}
  3. * in order to begin processing.
  4. *
  5. * @param cacheQueue 对传入请求进行分流的队列
  6. * @param networkQueue 需要请求网络的requests 的队列
  7. * @param cache Cache interface to use for resolution
  8. * @param delivery Delivery 接口用以分发返回数据
  9. */
  10. public CacheDispatcher(
  11. BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
  12. Cache cache, ResponseDelivery delivery) {
  13. mCacheQueue = cacheQueue;
  14. mNetworkQueue = networkQueue;
  15. mCache = cache;
  16. mDelivery = delivery;
  17. }

查看主要的run方法:

  1. @Override
  2. public void run() {
  3. if (DEBUG) VolleyLog.v("start new dispatcher");
  4. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  5. // Make a blocking call to initialize the cache.
  6. mCache.initialize();
  7. while (true) {
  8. try {
  9. // 这里是从mCacheQueue队列获取请求
  10. //注意这个队列的声明是BlockingQueue<Request<?>>
  11. //如果没有元素会是阻塞状态
  12. final Request<?> request = mCacheQueue.take();
  13. request.addMarker("cache-queue-take");
  14. // 检查请求是否已经被取消
  15. if (request.isCanceled()) {
  16. request.finish("cache-discard-canceled");
  17. continue;
  18. }
  19. // 尝试从缓存中去检索某一个请求
  20. Cache.Entry entry = mCache.get(request.getCacheKey());
  21. if (entry == null) {
  22. request.addMarker("cache-miss");
  23. // 缓存中没有,传入网络线程队列
  24. mNetworkQueue.put(request);
  25. continue;
  26. }
  27. // 如果已经过期,传入网络线程队列
  28. if (entry.isExpired()) {
  29. request.addMarker("cache-hit-expired");
  30. request.setCacheEntry(entry);
  31. mNetworkQueue.put(request);
  32. continue;
  33. }
  34. // 缓存区有次请求数据; 解析数据,回传给请求
  35. request.addMarker("cache-hit");
  36. Response<?> response = request.parseNetworkResponse(
  37. new NetworkResponse(entry.data, entry.responseHeaders));
  38. request.addMarker("cache-hit-parsed");
  39. if (!entry.refreshNeeded()) {
  40. // 没有过期,只需要回传数据即可
  41. mDelivery.postResponse(request, response);
  42. } else {
  43. // Soft-expired cache hit. We can deliver the cached response,
  44. // but we need to also send the request to the network for
  45. // refreshing.
  46. request.addMarker("cache-hit-refresh-needed");
  47. request.setCacheEntry(entry);
  48. // Mark the response as intermediate.
  49. response.intermediate = true;
  50. // Post the intermediate response back to the user and have
  51. // the delivery then forward the request along to the network.
  52. mDelivery.postResponse(request, response, new Runnable() {
  53. @Override
  54. public void run() {
  55. try {
  56. mNetworkQueue.put(request);
  57. } catch (InterruptedException e) {
  58. // Not much we can do about this.
  59. }
  60. }
  61. });
  62. }
  63. } catch (InterruptedException e) {
  64. // We may have been interrupted because it was time to quit.
  65. if (mQuit) {
  66. return;
  67. }
  68. continue;
  69. }
  70. }
  71. }

线程权重设置为THREAD_PRIORITY_BACKGROUND,然后是不断的从缓存队列中取对象,缓存队列采用了阻塞队列,当队列中没有元素中时处于阻塞状态,一直到有新元素添加进来。当取到对象,查验是否被取消,然后去缓存中查找,如果存在,检测是否过期,否则都会扔进网络线程。最后有个refreshNeeded方法,这个涉及是否服务端有过期策略的支持,下边再说。

看看比较重要的NetworkDispatcher
还是先看构造函数:

  1. /**
  2. * Creates a new network dispatcher thread. You must call {@link #start()}
  3. * in order to begin processing.
  4. *
  5. * @param queue Queue of incoming requests for triage
  6. * @param network Network interface to use for performing requests
  7. * @param cache Cache interface to use for writing responses to cache
  8. * @param delivery Delivery interface to use for posting responses
  9. */
  10. public NetworkDispatcher(BlockingQueue<Request> queue,
  11. Network network, Cache cache,
  12. ResponseDelivery delivery) {
  13. mQueue = queue;
  14. mNetwork = network;
  15. mCache = cache;
  16. mDelivery = delivery;
  17. }

不同的是多了一个真正执行网络请求的network对象。

看看重要是run方法体:

  1. @Override
  2. public void run() {
  3. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  4. Request request;
  5. while (true) {
  6. try {
  7. // 从队列中获取一个请求
  8. request = mQueue.take();
  9. } catch (InterruptedException e) {
  10. // We may have been interrupted because it was time to quit.
  11. if (mQuit) {
  12. return;
  13. }
  14. continue;
  15. }
  16. try {
  17. request.addMarker("network-queue-take");
  18. // 检测是否已经被取消
  19. // 取消就不执行网络操作
  20. if (request.isCanceled()) {
  21. request.finish("network-discard-cancelled");
  22. continue;
  23. }
  24. // Tag the request (if API >= 14)
  25. if (Build.VERSION.SDK_INT >= 14) {
  26. TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
  27. }
  28. // 执行网络请求,得到返回数据networkresponse
  29. NetworkResponse networkResponse = mNetwork.performRequest(request);
  30. request.addMarker("network-http-complete");
  31. // 如果服务端返回 304 我们已经分发过返回结果
  32. // 就此结束,不需要再发一遍结果
  33. if (networkResponse.notModified && request.hasHadResponseDelivered()) {
  34. request.finish("not-modified");
  35. continue;
  36. }
  37. // 在worker线程里解析返回的数据
  38. Response<?> response = request.parseNetworkResponse(networkResponse);
  39. request.addMarker("network-parse-complete");
  40. // 如果需要,写入缓存中
  41. // TODO: Only update cache metadata instead of entire record for 304s.
  42. if (request.shouldCache() && response.cacheEntry != null) {
  43. mCache.put(request.getCacheKey(), response.cacheEntry);
  44. request.addMarker("network-cache-written");
  45. }
  46. // 返回网络请求的结果
  47. request.markDelivered();
  48. mDelivery.postResponse(request, response);
  49. } catch (VolleyError volleyError) {
  50. parseAndDeliverNetworkError(request, volleyError);
  51. } catch (Exception e) {
  52. VolleyLog.e(e, "Unhandled exception %s", e.toString());
  53. mDelivery.postError(request, new VolleyError(e));
  54. }
  55. }
  56. }

网络请求的逻辑过程很清晰。这里我们再看看关于过期策略的问题。

  1. /** True if the entry is expired. */
  2. public boolean isExpired() {
  3. return this.ttl < System.currentTimeMillis();
  4. }
  5. /** True if a refresh is needed from the original data source. */
  6. public boolean refreshNeeded() {
  7. return this.softTtl < System.currentTimeMillis();
  8. }

这是判断是否过期,是否需要刷新的地方,其实就是用一个记录的时候看看是否超过当前时间。那就需要查查这个ttl,softttl时间戳是在哪设置的。跟进代码找到设置的地方。在HttpHeaderParser类中的parseCacheHeaders方法里会对网络返回的数据进行解析,返回一个Cache.Entry对象。这里会设置时间戳。具体代码如下:

  1. headerValue = headers.get("Cache-Control");
  2. if (headerValue != null) {
  3. hasCacheControl = true;
  4. String[] tokens = headerValue.split(",");
  5. for (int i = 0; i < tokens.length; i++) {
  6. String token = tokens[i].trim();
  7. if (token.equals("no-cache") || token.equals("no-store")) {
  8. return null;
  9. } else if (token.startsWith("max-age=")) {
  10. try {
  11. maxAge = Long.parseLong(token.substring(8));
  12. } catch (Exception e) {
  13. }
  14. } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
  15. maxAge = 0;
  16. }
  17. }
  18. }
  19. headerValue = headers.get("Expires");
  20. if (headerValue != null) {
  21. serverExpires = parseDateAsEpoch(headerValue);
  22. }
  23. serverEtag = headers.get("ETag");
  24. // Cache-Control takes precedence over an Expires header, even if both exist and Expires
  25. // is more restrictive.
  26. if (hasCacheControl) {
  27. softExpire = now + maxAge * 1000;
  28. } else if (serverDate > 0 && serverExpires >= serverDate) {
  29. // Default semantic for Expire header in HTTP specification is softExpire.
  30. softExpire = now + (serverExpires - serverDate);
  31. }
  32. Cache.Entry entry = new Cache.Entry();
  33. entry.data = response.data;
  34. entry.etag = serverEtag;
  35. entry.softTtl = softExpire;
  36. entry.ttl = entry.softTtl;
  37. entry.serverDate = serverDate;
  38. entry.responseHeaders = headers;

从返回的header中取serverExpiresCache-ControlExpires header具有更高的优先级,如果返回的header中包含Cache-Control,那就从header中获取maxAge,softExpire就是maxAge秒的有效期。否则就是从header中获取serverExpiresserverDate,两者相减就是有效期的数值。缓存中保存的对象有效期都被设置成了softExpire

最后附上整体流程图:
此处输入图片的描述

参考资料:
http://tomkeyzhang.duapp.com/?p=7
Volley源码:
https://github.com/mcxiaoke/android-volley
作者:flyouting

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