Volley 源码解析
开始之前
目前已经有很多文章写volley源码解析的了,
为什么这么多呢? 可能是因为volley 的源码相对来说比较少, 逻辑相对简单,好读。
为什么我还要写volley源码解析呢? 和上面的问题的原因一样, 还有就是我也读了好几遍volley的源码了,每次读完后过段时间就会忘记一些,应了那句老话,“好记性不如烂笔头”,现在也不用纸笔了,直接电脑上敲出来,更方便。 我不一定写的比其他同学的高明,但是我会尽量写出我的理解,和现实工作内容联系起来书写。
本文的目录结构是:
- volley 的基本使用
- volley 源码解析
- volley 的优缺点
- volley 的扩展
volley 基本使用
Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架。在 Google I/O 2013 大会上发布。
特别适合数据量小,通信频繁的网络操作
1 | 在项目的gradle 文件中添加依赖 |
使用起来很简单,举一个StringRequest例子,其他的类似
1 |
|
volley 源码解析
首先我们看下请求队列的创建。
Volley
1 | RequestQueue queue = Volley.newRequestQueue(this); |
1 |
|
先判断是否有指定 stack 也就是实际请求的方式,如果有传进来,就用传的来指定的方式,
如果为空, 就按照sdk 版本创建相应的请求方式, sdk版本<9的使用 httpClient 请求
大于等于9的使用 httpUrlconnection 来请求,这是因为,httpUrlconnection在Android 2.2之前有bug。但是httpClient 没有 httpUrlconnection 性能好, Api简单,体积较小,压缩和缓存机制也可有效减少网络访问的流量, 而且httpclient 请求的方式 在Android6.0之后也直接从sdk 中直接去掉了。
RequestQueue
1 | public RequestQueue(Cache cache, Network network, int threadPoolSize) { |
可以重点关注 ExecutorDelivery(new Handler(Looper.getMainLooper())))
接下来在线程切换地方法讲到它,这个是成功把子线程数据发送到主线程的关键
1 | 任务队列的启动方法 |
任务队列创建完成,等到request 加入,request的创建比较简单,可以根据自己的需求创建相应的request ,系统提供了 String ,Image, JsonObject ,jsonArray 等request,他们的区别主要是在 parseNetworkResponse 方法中根据不同的数据类型,进行相应数据类型的解析。
再看网络请求的 add 方法,就是把request 添加到 任务队列中
1 | public <T> Request<T> add(Request<T> request) { |
这个 mNetworkQueue 和 mCacheQueue 都是 PriorityBlockingQueue 优先级阻塞队列 现在根据情况分别加入到相应的调度线程等待执行
CacheDispatcher
上面已经把request加入到缓存队列中了,接下来看下 CacheDispatcher 是如何处理这些request的。
1 |
|
能看到这个 CacheDispatcher 继承 Thread ,前面我们也看到start 方法中已经 调用了线程的 start() ,那我们看下run方法
1 |
|
能看到代码相当简洁, 就是一个 while (true) 死循环, 里面执行 processRequest方法,
1 | private void processRequest() throws InterruptedException { |
英文注释写的也特别清楚,就是从 BlockingQueue阻塞队列中取出,如果有数据就处理,没有数据就阻塞在这。
BlockingQueue
如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒。
同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作。
了解了 BlockingQueue 我们看下真正处理request的详细方法
1 |
|
NetworkDispatcher
看完了 缓存队列的执行方式, 再看下网络请求线程的执行,跟上面的方法方式类似,我们直奔processRequest看它是如何实现的。
1 | void processRequest(Request<?> request) { |
具体执行网络请求的是 BasicNetwork 的 performRequest 方法
1 |
|
HurlStack
我们可以再看下建立网络链接的部分,这个是httpUrlcinnection,
1 |
|
总结上面这个方法就是 把所有的请求头信息发送出去,如果有请求体,把请求体也发送过去, 等待服务端相应,响应后把返回值回传到上一个方法中处理。
volley 是如何实现线程切换的
- 主线程上开启子线程执行网络请求在上面分析中能看到。 创建了1个缓存线程,4个网络请求线程,执行相应的网络请求。
- 子线程在处理完耗时操作,处理完数据,怎么发送到主线程的呢?
ExecutorDelivery
接下来看下我们上面多次看到的 mDelivery.postResponse(request, response); 是怎么处理的
先看下构造方法
1 | public ExecutorDelivery(final Handler handler) { |
通过这种方式,mResponsePoster.execute 是得里面的runnable能够在主线程中得到执行,
ResponseDeliveryRunnable
1 | // If this request has canceled, finish it and don't deliver. |
最后调用 request 的 finish 方法,表示该请求已经执行结束了,同时,如果 ResponseDeliveryRunnable 的构造方法中的第三个参数 runnable 不为空,立即执行该 runnable 的 run 方法。
再看这个Stringrequest中的
1 |
|
在主线程 通过接口回调,回调到发起请求的位置,进行相应的处理。
volley 的优缺点
优点:
- 特别适合数据量小,通信频繁的网络操作
- 轻量,jar包相对较小
- 扩展性强。Volley 中大多是基于接口的设计,可配置性强。
- 一定程度符合 Http 规范,包括返回 ResponseCode(2xx、3xx、4xx、5xx)的处理,请求头的处理,缓存机制的支持等。并支持重试及优先级定义。
- 默认 Android2.3 及以上基于 HttpURLConnection,2.3 以下基于 HttpClient 实现,
- 提供简便的图片加载工具。
- 网络请求线程NetworkDispatcher默认开启了4个,可以优化,通过手机CPU数量
缺点:
- 在BasicNetwork中判断了statusCode(statusCode < 200 || statusCode > 299),如何符合条件直接抛出IOException(),不够合理。
- 图片加载性能一般
- 导致401等其他状态抛出IOException
- 对大文件下载 Volley的表现非常糟糕
- 使用的是httpclient,HttpURLConnection。不过在android 6.0不支持httpclient了,如果想支持得添加org.apache.http.legacy.jar
为什么volley 不适合下载上传大文件?为什么适合数据量小的频率高的请求?
- Volley的网络请求线程池默认大小为4。意味着可以并发进行4个请求,大于4个,会排在队列中。
- Volley将整个response加载到内存并进行操作(可以是解析等操作)大文件可能会引起OOM
volley 的扩展
有时候我们整个项目在使用的volley 如果要替换成okhttp 的成本就比较高了, 那能不能方便简单的时候okhttp 的优秀的功能呢, 答案当然是可以的。
还记得这个吗? 最开始分析源码时,
1 |
|
如果我们传stack 进来,这个 stack 就是null 了,所以,我们只需要实现这个HttpStack 然后传进来就行了。
1 |
|
总结
总体来看,volley 源码还是比较简单的,结构清晰,代码量少,对于想读源码的同学还是一个比较不错的选择。有兴趣的可以尝试下。 也希望看这篇文章的同学能从本文中受益,也欢迎与我交流学习。
下一篇是分析 okHttp 的源码,敬请期待。