Volley工作原理与设计模式

前言:

Volley是一个发布以来流行至今的网络框架,他的好处也人尽皆知。高并发、不易OOM、代码简介、可拓展性强等等,几乎大多数项目都会集成它或它的改良版。但Volley为什么在高并发上性能好?官网为什么说它只适合体积小的网络请求呢?

通过本章我们可以了解如下问题:

1.了解Volley的工作原理。

2.在同时有两个相同的请求时,如何优化处理?

3.请求数量大、资源竞争激烈的情况,如何优化线程?

4.为什么说Volley只适合于小数据请求(不超过3M)?

目录:

一、简单介绍Volley的工作流程

二、NetworkDispatcher工作原理

三、CacheDispatcher工作原理


一、简单介绍Volley的工作流程,包含如下知识点:

1.了解Volley的工作原理。

2.在同时有两个相同的请求时,如何优化处理?

我们从一个获取String类型数据的网络请求例子来阐述Volley的基本工作流程。

[java] view
plain
 copy

  1. RequestQueue requestQueue = Volley.newRequestQueue(this);
  2. StringRequest stringRequest = new StringRequest("http://www.google.com", new Response.Listener<String>() {
  3. @Override
  4. public void onResponse(String response) {
  5. }
  6. }, new Response.ErrorListener() {
  7. @Override
  8. public void onErrorResponse(VolleyError error) {
  9. }
  10. });
  11. requestQueue.add(stringRequest);

Volley.java

首先是RequestQueue对象的创建,Volley.newRequestQueue(this)方法是直接调用Volley工具类

[java] view
plain
 copy

  1. public static RequestQueue newRequestQueue(Context context) {
  2. return newRequestQueue(context, null);
  3. }

继续向下调

[java] view
plain
 copy

  1. public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
  2. //create cache file
  3. File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
  4. String userAgent = "volley/0";
  5. try {
  6. String packageName = context.getPackageName();
  7. PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
  8. userAgent = packageName + "/" + info.versionCode;
  9. } catch (NameNotFoundException e) {
  10. }
  11. if (stack == null) {
  12. if (Build.VERSION.SDK_INT >= 9) {
  13. stack = new HurlStack();
  14. } else {
  15. // Prior to Gingerbread, HttpUrlConnection was unreliable.
  16. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
  17. stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
  18. }
  19. }
  20. Network network = new BasicNetwork(stack);
  21. RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
  22. queue.start();
  23. return queue;
  24. }

这里面会真正创建一个RequestQueue对象然后返回它。RequestQueue相当于一个调度员,负责调度所有事物。

直接看第26行,start()方法是开启网络请求线程和缓存请求线程的入口。

RequestQueue.java

接下来看start()代码。

[java] view
plain
 copy

  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. }

主要流程:

1.如果线程已经在运行,关闭所有线程(2行);

2.开启一个缓存线程(4 - 5行);

3.开启4个网络线程(8 - 13行);

五个线程开启后,我们在自己的Java类中写的这个方法会将Request请求对象放入任务队列中,但具体是放在哪个队列里面呢?这就要从在文章最上面我们自己写的最后一行代码requestQueue.add(stringRequest);这个方法寻找答案了。

RequestQueue.java

add()源码有4个主要的队列对象

mCurrentRequests:用来记录所有任务对象,每有一个网络请求,都会加入到这个队列中,而如果完成任务或者取消任务后,会把这个Request移除队列。

mNetworkQueue:网络请求队列,如果有Request对象加入到这个队列则直接处理。

mCacheQueue:缓存队列,加入队列后会检测有无缓存,如果没有缓存或者是过期缓存则转入到mNetworkQueue队列中。

mWaitingRequests:Map类型,其中可以存储若干个Queue<Request<?>队列,用来存储相同请求的Request对象。举个例子,比如2个网络请求R1,R2都想通过某URL获取数据,R1率先请求,请求过程中R2开始请求,由于R1还在未完成所以还在mWaitingRequests中的某一Queue<Request<?>队列中,所以R2就自动加入到这个队列中,等待R1请求完毕后会把同一个请求结果发给R1和R2。

[java] view
plain
 copy

  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. //Url
  18. String cacheKey = request.getCacheKey();
  19. if (mWaitingRequests.containsKey(cacheKey)) {
  20. // There is already a request in flight. Queue up.
  21. Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
  22. if (stagedRequests == null) {
  23. stagedRequests = new LinkedList<Request<?>>();
  24. }
  25. stagedRequests.add(request);
  26. mWaitingRequests.put(cacheKey, stagedRequests);
  27. if (VolleyLog.DEBUG) {
  28. VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
  29. }
  30. } else {
  31. // Insert ‘null‘ queue for this cacheKey, indicating there is now a request in
  32. // flight.
  33. mWaitingRequests.put(cacheKey, null);
  34. mCacheQueue.add(request);
  35. }
  36. return request;
  37. }
  38. }

主要流程:

1、当前Request对象加入当前队列mCurrentRequests中,设置序列号,添加标记(3 - 10行);

2、如果当前Request对象不允许缓存,直接放如网络队列中(13 - 16行);

3、获取URL,在mWaitingRequests中查找是否有相同URL请求,没有则加入到缓存队列,有则在mWaitingRequests中对应的队列中加入自己,等待那个相同的URL请求完成后直接把成果复制过来(19 - 38行);

这里用到了一个设计模式—"生产者消费者模式”,即通过一个容器来解决强耦合问题,生产者、消费者之间不通讯,生产者只负责生产产品放到阻塞队列中,消费者只负责从阻塞队列中获取产品,平衡了生产者和消费者的处理能力,避免生产者与消费者捆绑的问题,提高了并发处理的能力。

在Volley里面,生产者不一定只是主线程,也有可能是CacheDispatcher,上一个消费者可能是下一个消费者的生产者,阻塞队列是网络请求队列和缓存请求队列。


二、NetworkDispatcher工作原理,包含如下问题

3.请求数量大、资源竞争激烈的情况,如何优化线程?

4.为什么说Volley只适合于小数据请求(不超过3M)?

第一次真正用到NetworkDispatcher的run方法是在上面的第14行代码mNetworkQueue.add(request)

NetworkDispatcher.java

[java] view
plain
 copy

  1. public void run() {
  2. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  3. while (true) {
  4. long startTimeMs = SystemClock.elapsedRealtime();
  5. Request<?> request;
  6. try {
  7. // Take a request from the queue.
  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. // If the request was cancelled already, do not perform the
  19. // network request.
  20. if (request.isCanceled()) {
  21. request.finish("network-discard-cancelled");
  22. continue;
  23. }
  24. addTrafficStatsTag(request);
  25. // Perform the network request.
  26. NetworkResponse networkResponse = mNetwork.performRequest(request);
  27. request.addMarker("network-http-complete");
  28. // If the server returned 304 AND we delivered a response already,
  29. // we‘re done -- don‘t deliver a second identical response.
  30. if (networkResponse.notModified && request.hasHadResponseDelivered()) {
  31. request.finish("not-modified");
  32. continue;
  33. }
  34. // Parse the response here on the worker thread.
  35. Response<?> response = request.parseNetworkResponse(networkResponse);
  36. request.addMarker("network-parse-complete");
  37. // Write to cache if applicable.
  38. // TODO: Only update cache metadata instead of entire record for 304s.
  39. if (request.shouldCache() && response.cacheEntry != null) {
  40. mCache.put(request.getCacheKey(), response.cacheEntry);
  41. request.addMarker("network-cache-written");
  42. }
  43. // Post the response back.
  44. request.markDelivered();
  45. mDelivery.postResponse(request, response);
  46. } catch (VolleyError volleyError) {
  47. volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
  48. parseAndDeliverNetworkError(request, volleyError);
  49. } catch (Exception e) {
  50. VolleyLog.e(e, "Unhandled exception %s", e.toString());
  51. VolleyError volleyError = new VolleyError(e);
  52. volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
  53. mDelivery.postError(request, volleyError);
  54. }
  55. }
  56. }

主要流程:

1、查看mCacheQueue队列中是否有Request对象。有,向下执行;没有,则阻塞当前线程(8行);

那么问题来了,它是如何阻塞和唤醒的线程?(后面会讲)先讨论另一个问题,在任务队列里面有几十上百张图片或字符串等等的加载任务,而此时4个网络线程来不断通过这个任务队列来领取其中的任务来完成资源请求,该如何加锁防止同一资源被多个线程拿到呢?我的反应是用”synchronized”,但点进去看源码才发现不是。

PriorityBlockingQueue.java部分源码

(PriorityBlockingQueue.java)502 - 503行,synchronized和ReentrantLock同为锁机制,synchronized为同步锁的首选,这里使用ReentrantLock对象来管理线程锁,那为什么不用synchronized呢?

ReentrantLock是synchronized不适用时的另一种可选择的高级功能,这里选择用ReentrantLock主要是其在高资源竞争下性能的高效和相对于内置锁,显示锁灵活的操作;但如果是资源竞争不激烈的情况下表现不如synchronized,所以这也是为什么volley在下载大文件时表现不如意的原因之一。这里ReentrantLock使用lockInterruptibly()来获取锁,是因为如果遇到网络或者其他问题该线程不能正常下载可随时中断该线程并向上层代码抛出异常,这样如果一个网络请求失败了取消掉重新获取下一个任务。

(PriorityBlockingQueue.java)506 - 507行,获取队列任务,如果队列内容为空,notEmpty.await()将当前线程阻塞,并释放锁(注意:不是通过unlock()解锁)。知道了阻塞,那如何被唤醒呢?

在RequestQueue类中向队列中添加Request对象时调用的add()方法最终会调用到PriorityBlockingQueue类中的offer()方法,offer()中会调用到Condition对象中的signal()方法来唤起当前线程。这样中断和唤起的流程就通了。

详情请参见ReentrantLock显示锁

2、对interruptibly解释做出了诠释,出现中断请求直接退出(10 - 16行)。

3、进行网络请求,这里深讲的话涉及很多的内容,将在下一章讲解(31行)。

4、将返回来的数据,根据对应类型进行解析(42行)。

5、如果需要缓存则将缓存内容加入到mCache队列里面,下一章讲解(47 - 50行)。

6、将解析内容发送给最初的请求端(53 - 54行)。

总结:逻辑清晰,读懂之后会对线程并发有了进一步的认识。


三、CacheDispatcher工作原理

如果读懂了NetworkDispatcher的工作原理,那么CacheDispatcher也自然会明白了,因为前者几乎覆盖了后者的知识点。

至此三个重要的模块已经全部了解完成。

针对于Volley的设计原理,RequestQueue相当于一个调度员,负责处理各种繁琐的事物,一个app只适合创建一个RequestQueue对象,否则会违背Volley“多执行少调度”的基本原则。

时间: 2024-10-08 22:20:23

Volley工作原理与设计模式的相关文章

Tomcat 系统架构与设计模式,第 1 部分: 工作原理

Tomcat 系统架构与设计模式,第 1 部分: 工作原理 这个分为两个部分的系列文章将研究 Apache Tomcat 的系统架构以及其运用的很多经典设计模式.本文是第 1 部分,将主要从 Tomcat 如何分发请求.如何处理多用户同时请求,还有它的多级容器是如何协调工作的角度来分析 Tomcat 的工作原理,这也是一个 Web 服务器首要解决的关键问题. 这个分为两个部分的系列文章将研究 Apache Tomcat 的系统架构以及其运用的很多经典设计模式.本文是第 1 部分,将主要从 Tom

【Tomcat】Tomcat 系统架构与设计模式,第 1 部分: 工作原理

这个分为两个部分的系列文章将研究 Apache Tomcat 的系统架构以及其运用的很多经典设计模式.本文是第 1 部分,将主要从 Tomcat 如何分发请求.如何处理多用户同时请求,还有它的多级容器是如何协调工作的角度来分析 Tomcat 的工作原理,这也是一个 Web 服务器首要解决的关键问题. 本文以 Tomcat 5 为基础,也兼顾最新的 Tomcat 6 和 Tomcat 4.Tomcat 的基本设计思路和架构是具有一定连续性的. Tomcat 总体结构 Tomcat 的结构很复杂,但

[Java] SSH框架笔记_SSH三大框架的工作原理及流程

Hibernate工作原理及为什么要用? 原理:1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件2.由hibernate.cfg.xml中的<mapping resource="com/xx/User.hbm.xml"/>读取并解析映射信息3.通过config.buildSessionFactory();//创建SessionFactory4.sessionFactory.openSession();//打

Servlet 工作原理解析

-----转自许令波老师Servlet 工作原理解析  感觉写的很不错,保存下来,留着以后温习 从 Servlet 容器说起 要介绍 Servlet 必须要先把 Servlet 容器说清楚,Servlet 与 Servlet 容器的关系有点像枪和子弹的关系,枪是为子弹而生,而子弹又让枪有了杀伤力.虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化生产的结果.从技术角度来说是为了解耦,通过标准化接口来相互协作.既然接口是连接 Servlet 与 Servlet 容器的关键,那我们就

SSH三大框架的工作原理及流程

Hibernate工作原理及为什么要用? 原理:1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件2.由hibernate.cfg.xml中的<mapping resource="com/xx/User.hbm.xml"/>读取并解析映射信息3.通过config.buildSessionFactory();//创建SessionFactory4.sessionFactory.openSession();//打

Volley 实现原理解析(转)

Volley 实现原理解析 转自:http://blog.csdn.net/fengqiaoyebo2008/article/details/42963915 1. 功能介绍 1.1. Volley Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架.在 Google I/O 2013 大会上发布. 名字由来:a burst or emission of many things or a large amount at once发布演讲时候的配图 从名字由来和

Hibernate 工作原理及为什么要用

Hibernate工作原理及为什么要用? 原理:1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件2.由hibernate.cfg.xml中的<mapping resource="com/xx/User.hbm.xml"/>读取并解析映射信息3.通过config.buildSessionFactory();//创建SessionFactory4.sessionFactory.openSession();//打

Jetty 的工作原理以及与 Tomcat 的比较

Jetty 应该是目前最活跃也是很有前景的一个 Servlet 引擎.本文将介绍 Jetty 基本架构与基本的工作原理:您将了解到 Jetty 的基本体系结构:Jetty 的启动过程:Jetty 如何接受和处理用户的请求.你还将了解到 AJP 的一些细节:Jetty 如何基于 AJP 工作:以及 Jetty 如何集成到 Jboss:最后我们将比较一下两个 Servlet 引擎:Tomcat 和 Jetty 的优缺点. Jetty 的基本架构 Jetty 目前的是一个比较被看好的 Servlet

struts2的核心和工作原理

在学习struts2之前,首先我们要明白使用struts2的目的是什么?它能给我们带来什么样的好处? 设计目标 Struts设计的第一目标就是使MVC模式应用于web程序设计.在这儿MVC模式的好处就不在提了. 技术优势 Struts2有两方面的技术优势,一是所有的Struts2应用程序都是基于client/server HTTP交换协议,The Java Servlet API揭示了Java Servlet只是Java API的一个很小子集,这样我们可以在业务逻辑部分使用功能强大的Java语言