技术控

    今日:227| 主题:58200
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Android Volley源码分析(一)

[复制链接]
三千哽咽 投递于 2016-10-15 15:43:25
590 16
volley 是一个非常流行的 Android 开源框架,自己平时也经常使用它,但自己对于它的内部的实现过程并没有进行太多的深究。所以为了以后能更通透的使用它,了解它的实现是一个非常重要的过程。自己有了一点研究,做个笔记同时与大家一起分享。期间自己也画了一张图,希望能更好的帮助我们理解其中的步骤与原理。如下:
   

Android Volley源码分析(一)

Android Volley源码分析(一)-1-技术控-Android,public,图片,流行,网络
    开始看可能会一脸懵逼,我们先结合源码一步一步来,现在让我们一起进入 Volley 的源码世界,来解读大神们的编程思想。
    newRequestQueue

   如果使用过 Volley 的都知道,不管是进行网络请求还是什么别的图片加载,首先都要创建好一个 RequestQueue
  1. public static final RequestQueue volleyQueue = Volley.newRequestQueue(App.mContext);
复制代码
  而 RequestQueue 的创建自然离不开 Volley 中的静态方法 newRequestQueue ,从上面的图片也能知道首先进入点是 newRequestQueue ,好了现在我们来看下该方法中到底做了什么:
  1. public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
  2. File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
  3. String userAgent = "volley/0";
  4. try {
  5. String packageName = context.getPackageName();
  6. PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
  7. userAgent = packageName + "/" + info.versionCode;
  8. } catch (NameNotFoundException e) {
  9. }
  10. if (stack == null) {
  11. if (Build.VERSION.SDK_INT >= 9) {
  12. stack = new HurlStack();
  13. } else {
  14. // Prior to Gingerbread, HttpUrlConnection was unreliable.
  15. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
  16. stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
  17. }
  18. }
  19. Network network = new BasicNetwork(stack);
  20. RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
  21. queue.start();
  22. return queue;
  23. }
复制代码
  从上面的源码中我们能发现有一个 stack ,它代表着网络请求通信 HttpClient 与 HttpUrlConnection ,但我们一般都是默认设置为 null 。因为它会默认帮我们进判断选择更适合的。当手机版本大于等于 9 时会使用 HurlStack 它里面使用 HttpUrlConnection 进行实现通信的,而小于 9 时则创建 HttpClientStack ,它里面自然是 HttpClient 的实现。通过 BasicNetwork 构造成 Network 来进行传递使用;在这之后构建了 RequestQueue ,其中帮我们构造了一个缓存 new DiskBasedCache(cacheDir) ,默认为 5MB ,缓存目录为 volley 。所以我们能在 data/data/应用包名/volley 下找到缓存。最后调用其 start 方法,并返回 RequestQueue 。这就是我们前面第一段代码的内部实现。下面我们进入 RequestQueue 中的 start 方法看下它到底做了什么呢?
  RequestQueue

   在 RequestQueue 中通过 this 调用自身来默认帮我们调用了下面的构造函数
  1. public RequestQueue(Cache cache, Network network, int threadPoolSize,
  2. ResponseDelivery delivery) {
  3. mCache = cache;
  4. mNetwork = network;
  5. mDispatchers = new NetworkDispatcher[threadPoolSize];
  6. mDelivery = delivery;
  7. }
复制代码
  cache 后续在 NetworkDispatcher 中会帮我们进行 response.cacheEntry 的缓存, netWork 是前面的根据版本所封装的通信, threadPoolSize 线程池大小默认为 4 , delivery ,是 ExecutorDelivery 作用在主线程,在最后对请求响应的分发。
  start

  1. public void start() {
  2. stop(); // Make sure any currently running dispatchers are stopped.
  3. // Create the cache dispatcher and start it.
  4. mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
  5. mCacheDispatcher.start();
  6. // Create network dispatchers (and corresponding threads) up to the pool size.
  7. for (int i = 0; i < mDispatchers.length; i++) {
  8. NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
  9. mCache, mDelivery);
  10. mDispatchers[i] = networkDispatcher;
  11. networkDispatcher.start();
  12. }
  13. }
复制代码
  在 start 方法中我们发现它实现了两个 dispatch ,一个是 CacheDispatcher 缓存派遣,另一个是 networkDispatcher 进行网络派遣,其实他们都是 Thread ,所以都调用了他们的 start 方法。其中 networkDispatcher 默认构建了 4 个,相当于包含 4 个线程的线程池。现在我们先不去看他们内部的 run 方法到底实现了什么,我们还是接着看 RequestQueue 中我们频繁使用的 add 方法。
  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. // Insert request into stage if there's already a request with the same cache key in flight.
  16. synchronized (mWaitingRequests) {
  17. String cacheKey = request.getCacheKey();
  18. if (mWaitingRequests.containsKey(cacheKey)) {
  19. // There is already a request in flight. Queue up.
  20. Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
  21. if (stagedRequests == null) {
  22. stagedRequests = new LinkedList<Request<?>>();
  23. }
  24. stagedRequests.add(request);
  25. mWaitingRequests.put(cacheKey, stagedRequests);
  26. if (VolleyLog.DEBUG) {
  27. VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
  28. }
  29. } else {
  30. // Insert 'null' queue for this cacheKey, indicating there is now a request in
  31. // flight.
  32. mWaitingRequests.put(cacheKey, null);
  33. mCacheQueue.add(request);
  34. }
  35. return request;
  36. }
  37. }
复制代码
  我们结合代码与前面的图,首先会为当前的 request 设置 RequestQueue ,并且根据情况同步是否是当前正在进行的请求,加入到 mCurrentRequests 中,看 11 行代码,之后对当前 request 进行判断是否需要缓存(默认实现是 true ,如果不需要可调用 request.setShouldCache() 进行设置),如果不需要则直接加入到前面的 mNetworkQueue 中,它会在 CacheDispatcher 与 NetworkDispatcher 中做相应的处理,然后返回 request 。如果需要缓存,看16行代码,则对 mWaitingRequests 中是否包含 cacheKey 进行相应的处理。其中 cacheKey 为请求的 url 。最后再加入到缓存队列 mCacheQueue 中。
  finish

   细心的人会发现当对应 cacheKey 的 value 不为空时,创建了 LinkedList 即 Queue ,只是将 request 加入到了 Queue 中,只是更新了 mWaitingRequests 中相应的 value 但并没有加入到 mCacheQueue 中。其实不然,因为后续会调用 finish 方法,我们来看下源码:
  1. <T> void finish(Request<T> request) {
  2. // Remove from the set of requests currently being processed.
  3. synchronized (mCurrentRequests) {
  4. mCurrentRequests.remove(request);
  5. }
  6. synchronized (mFinishedListeners) {
  7. for (RequestFinishedListener<T> listener : mFinishedListeners) {
  8. listener.onRequestFinished(request);
  9. }
  10. }
  11. if (request.shouldCache()) {
  12. synchronized (mWaitingRequests) {
  13. String cacheKey = request.getCacheKey();
  14. Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
  15. if (waitingRequests != null) {
  16. if (VolleyLog.DEBUG) {
  17. VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
  18. waitingRequests.size(), cacheKey);
  19. }
  20. // Process all queued up requests. They won't be considered as in flight, but
  21. // that's not a problem as the cache has been primed by 'request'.
  22. mCacheQueue.addAll(waitingRequests);
  23. }
  24. }
  25. }
  26. }
复制代码
  看 14 、 22 行代码,正如上面我所说,会将 Queue 中的 request 全部加入到 mCacheQueue 中。
   好了 RequestQueue 的主要源码差不多就这些,下面我们进入 CacheDispatcher 的源码分析,看它究竟如何工作的呢?
  CacheDispatcher

   前面提到了它与 NetworkDispatcher 本质都是 Thread ,那么我们自然是看 run 方法
  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. // Get a request from the cache triage queue, blocking until
  10. // at least one is available.
  11. final Request<?> request = mCacheQueue.take();
  12. request.addMarker("cache-queue-take");
  13. // If the request has been canceled, don't bother dispatching it.
  14. if (request.isCanceled()) {
  15. request.finish("cache-discard-canceled");
  16. continue;
  17. }
  18. // Attempt to retrieve this item from cache.
  19. Cache.Entry entry = mCache.get(request.getCacheKey());
  20. if (entry == null) {
  21. request.addMarker("cache-miss");
  22. // Cache miss; send off to the network dispatcher.
  23. mNetworkQueue.put(request);
  24. continue;
  25. }
  26. // If it is completely expired, just send it to the network.
  27. if (entry.isExpired()) {
  28. request.addMarker("cache-hit-expired");
  29. request.setCacheEntry(entry);
  30. mNetworkQueue.put(request);
  31. continue;
  32. }
  33. // We have a cache hit; parse its data for delivery back to the request.
  34. request.addMarker("cache-hit");
  35. Response<?> response = request.parseNetworkResponse(
  36. new NetworkResponse(entry.data, entry.responseHeaders));
  37. request.addMarker("cache-hit-parsed");
  38. if (!entry.refreshNeeded()) {
  39. // Completely unexpired cache hit. Just deliver the response.
  40. mDelivery.postResponse(request, response);
  41. } else {
  42. // Soft-expired cache hit. We can deliver the cached response,
  43. // but we need to also send the request to the network for
  44. // refreshing.
  45. request.addMarker("cache-hit-refresh-needed");
  46. request.setCacheEntry(entry);
  47. // Mark the response as intermediate.
  48. response.intermediate = true;
  49. // Post the intermediate response back to the user and have
  50. // the delivery then forward the request along to the network.
  51. mDelivery.postResponse(request, response, new Runnable() {
  52. @Override
  53. public void run() {
  54. try {
  55. mNetworkQueue.put(request);
  56. } catch (InterruptedException e) {
  57. // Not much we can do about this.
  58. }
  59. }
  60. });
  61. }
  62. } catch (InterruptedException e) {
  63. // We may have been interrupted because it was time to quit.
  64. if (mQuit) {
  65. return;
  66. }
  67. continue;
  68. }
  69. }
  70. }
复制代码
  看起来很多,我们结合图来挑主要的看。首先创建了一个无限循环一直在监视着 request 的变化。从缓存队列 mCacheQueue 中获取 request ,如果该请求是 cancle 了,调用 request.finish() 清除相应数据并进行下一个请求的操作,否则从前面提到的 mCache 中获取 Cache.Entry 。如果不存在或者已经过期,将请求加入到网络队列中 mNetWorkQueue ,进行后续的网络请求。如果存在( 41 行代码)则进行 request.parseNetworkResponse() 解析出 response ,不同的 request 对应不同的解析方法。例如 StringRequest 与 JsonObjectRequest 有各自的解析实现。再看 45 行,发现不管 entry 是否需要更新的,都会进一步对 response 进行 mDelivery.postResponse(request, response) 递送,不同的是需要更新的话重新设置 request 的 entry 与加入到 mNetworkQueue 中,也就相当与重新进行网络请求一遍。那么再回到递送的阶段,前面已经提到在创建 RequestQueue 是实现了 ExecutorDelivery , mDelivery.postResponse 就是其中的方法。我们来看一下
  ExecutorDelivery

   在这里创建了一个 Executor ,对后面进行递送,作用在主线程
  1. public ExecutorDelivery(final Handler handler) {
  2. // Make an Executor that just wraps the handler.
  3. mResponsePoster = new Executor() {
  4. @Override
  5. public void execute(Runnable command) {
  6. handler.post(command);
  7. }
  8. };
  9. }
复制代码
postResponse

  1. @Override
  2. public void postResponse(Request<?> request, Response<?> response) {
  3. postResponse(request, response, null);
  4. }
  5. @Override
  6. public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
  7. request.markDelivered();
  8. request.addMarker("post-response");
  9. mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
  10. }
复制代码
  这个方法就简单了就是调用 execute 进行执行,在进入 ResponseDeliveryRunnable 的 run 看它如何执行
  ResponseDeliveryRunnable

  1. public void run() {
  2. // If this request has canceled, finish it and don't deliver.
  3. if (mRequest.isCanceled()) {
  4. mRequest.finish("canceled-at-delivery");
  5. return;
  6. }
  7. // Deliver a normal response or error, depending.
  8. if (mResponse.isSuccess()) {
  9. mRequest.deliverResponse(mResponse.result);
  10. } else {
  11. mRequest.deliverError(mResponse.error);
  12. }
  13. // If this is an intermediate response, add a marker, otherwise we're done
  14. // and the request can be finished.
  15. if (mResponse.intermediate) {
  16. mRequest.addMarker("intermediate-response");
  17. } else {
  18. mRequest.finish("done");
  19. }
  20. // If we have been provided a post-delivery runnable, run it.
  21. if (mRunnable != null) {
  22. mRunnable.run();
  23. }
  24. }
复制代码
  主要是第 9 行代码,对于不同的响应做不同的递送, deliverResponse 与 deliverError 内部分别调用的就是我们非常熟悉的 Listener 中的 onResponse 与 onErrorResponse 方法,进而返回到我们对网络请求结果的处理函数。
  这就是整个的缓存派遣,简而言之,存在请求响应的缓存数据就不进行网络请求,直接调用缓存中的数据进行分发递送。反之执行网络请求。
   下面我来看下 NetworkDispatcher 是如何处理的
  NetworkDispatcher

  run

  1. public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
  2. File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
  3. String userAgent = "volley/0";
  4. try {
  5. String packageName = context.getPackageName();
  6. PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
  7. userAgent = packageName + "/" + info.versionCode;
  8. } catch (NameNotFoundException e) {
  9. }
  10. if (stack == null) {
  11. if (Build.VERSION.SDK_INT >= 9) {
  12. stack = new HurlStack();
  13. } else {
  14. // Prior to Gingerbread, HttpUrlConnection was unreliable.
  15. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
  16. stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
  17. }
  18. }
  19. Network network = new BasicNetwork(stack);
  20. RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
  21. queue.start();
  22. return queue;
  23. }
  24. 0
复制代码
  在这里也创建了一个无限循环的 while ,同样也是先获取 request ,不过是从 mQueue 中,即前面多次出现的 mNetWorkQueue ,通过看代码发现一些实现跟 CacheDispatcher 中的类似。也正如图片中所展示的一样,( 23 行)如何请求取消了,直接 finish ;否则进行网络请求,调用( 31 行) mNetwork.performRequest(request) ,这里的 mNetWork 即为前面 RequestQueue 中对不同版本进行选择的 stack 的封装,分别调用 HurlStack 与 HttpClientStack 各自的 performRequest 方法,该方法中构造请求头与参数分别使用 HttpClient 或者 HttpUrlConnection 进行网络请求。我们再来看 42 行,是不是很熟悉,与 CacheDispatcher 中的一样进行 response 进行解析,然后如果需要缓存就加入到缓存中,最后( 54 行)再调用 mDelivery.postResponse(request, response) 进行递送。至于后面的剩余的步骤与 CacheDispatcher 中的一模一样,这里就不多累赘了。
   好了, Volley 的源码解析先就到这里了,我们再回过去看那张图是不是感觉很清晰了呢?
  总结

   我们来对使用 Volley 网络请求做个总结
  
       
  • 通过 newRequestQueue 初始化与构造 RequestQueue   
  • 调用 RequestQueue 中的 add 方法添加 request 到请求队列中   
  • 缓存派遣,先进行 CacheDispatcher ,判断缓存中是否存在,有则解析 response ,再直接 postResponse 递送,否则进行后续的网络请求   
  • 网络派遣, NetworkDispatcher 中进行相应的 request 请求,解析 response 如设置了缓存就将结果保存到 cache 中,再进行最后的 postResponse 递送。  
   转载请指明出处idisfkj博客: https://idisfkj.github.io



上一篇:Node.js 模块系统
下一篇:项目总结:游船管理系统
会飞的鱼 投递于 2016-10-15 17:51:50
开往春天的坦克!
回复 支持 反对

使用道具 举报

袖浅笼温柔 投递于 2016-10-16 14:03:29
三千哽咽总是默默地一个人奋斗,无人理会,而我来此祝你一臂之力。
回复 支持 反对

使用道具 举报

得意的笑 投递于 2016-10-20 05:00:44
什么啊,语文是苍老师教的吗?
回复 支持 反对

使用道具 举报

我没来 投递于 2016-10-24 14:10:57
楼下各位,今年发大财!
回复 支持 反对

使用道具 举报

梦里花落知多少 投递于 2016-10-30 10:45:15
我也顶起出售广告位
回复 支持 反对

使用道具 举报

陈果 投递于 2016-10-30 15:04:57
你会变成大海,然后被鲨鱼吃掉!
回复 支持 反对

使用道具 举报

操蛋 投递于 2016-11-3 00:50:37
当你的眼泪忍不住要流出来的时候,睁大眼睛,千万别眨眼,你会看到世界由清晰到模糊的全过程
回复 支持 反对

使用道具 举报

jesusNaky 投递于 2016-11-10 03:42:16
怀揣两块,胸怀500万!  
回复 支持 反对

使用道具 举报

Lee-CL 投递于 2016-11-10 16:19:19
宇宙第一贴诞生了!
回复 支持 反对

使用道具 举报

我要投稿

推荐阅读


回页顶回复上一篇下一篇回列表
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 | 粤公网安备 44010402000842号 )

© 2001-2017 Comsenz Inc.

返回顶部 返回列表