0001B
在 2013 年的 Google I/O 大会上,Volley 网络通信框架正式发布。Volley 框架被设计为适用于网络请求非常频繁但是数据量并不是特别大的情景,正如它的名字一样。Volley 相比其他网络框架而言,采用了在 Android 2.3 以下使用 HttpClient ,而 Android 2.3 及以上使用 HttpUrlConnection 的方案。这是因为在 Android 2.3 以下时,HttpUrlConnection 并不完善,有很多 bug 存在。因此在 Android 2.3 以下最好使用 HttpClient 来进行网络通信;而在 Android 2.3 及以上,HttpUrlConnection 比起 HttpClient 来说更加简单易用,修复了之前的 bug 。所以在 Android 2.3 及以上我们使用 HttpUrlConnection 来进行网络通信。
除此之外,Volley 框架还具有优先级处理、可扩展性强等特点。虽然现在有 Retrofit 、OkHttp 等十分优秀的网络通信框架,但是深入理解 Volley 框架内部的思想可以大大提高我们自身的技术水平,毕竟仅仅停留在只会使用的阶段是不行的哦。那么,下面就进入我们今天的正题吧!( ps :本文篇幅过长,可能会引起不适,请在家长的陪同下观看)
0010B
Volley 使用方法
在长篇大论地解析 Volley 框架源码之前,我们先来看看平时是怎样使用 Volley 的。(大牛可直接跳过 -_- )
1 | RequestQueue mQueue = Volley.newRequestQueue(context); |
我们通过 Volley.newRequestQueue(context)
来得到一个请求队列的对象 mQueue
,在队列中暂存了我们所有 add 进去的 request ,之后一个个取出 request 来进行网络通信。一般来说,在一个应用程序中,只保持一个请求队列的对象。
之后创建了 JsonObjectRequest 对象用来请求 JSON 数据,并把它加入 mQueue
的队列中。Volley 框架的使用方法非常简单,并且有多种 request 请求方式可以选择,使用方法都是和上面类似的。
0011B
在这先把 Volley 框架中几个重要的类的作用讲一下,以便看源码时能够更加明白:
- RequestQueue :这个大家一看都明白,用来缓存 request 的请求队列,根据优先级高低排列;
- Request :表示网络请求,本身是一个抽象类,子类有 StringRequest 、JsonRequest 、ImageRequest 等;
- Response :表示网络请求后的响应,也是一个抽象类。内部定义了 Listener 、ErrorListener 接口;
- NetworkResponse :对返回的 HttpResponse 内容进行了封装,虽然类名和 Response 差不多,但是不是 Response 的子类;
- CacheDispatcher :一个处理请求缓存的线程。不断从 RequestQueue 中取出 Request ,然后取得该 Request 对应的缓存,若缓存存在就调用 ResponseDelivery 做后续分发处理;如果没有缓存或者缓存失效需要进入 NetworkDispatcher 中从网络上获取结果;
- NetworkDispatcher :一个处理网络请求的线程。和 CacheDispatcher 类似,从网络上得到响应后调用 ResponseDelivery 做后续分发处理。而且根据需求判断是否需要做缓存处理;
- ResponseDelivery :用作分发处理。利用 Handler 把结果回调到主线程中,即 Listener 、ErrorListener 接口。主要实现类为 ExecutorDelivery ;
- HttpStack :主要作用就是发起 Http 请求。子类分为 HurlStack 和 HttpClientStack ,分别对应着 HttpUrlConnection 和 HttpClient ;
- Network :处理 Stack 发起的 Http 请求,把 Request 转换为 Response ,主要实现类为 BasicNetwork ;
- RetryPolicy :请求重试策略。主要实现类为 DefaultRetryPolicy ;
- Cache :网络请求的缓存。在 CacheDispatcher 中获取 Cache ,在 NetworkDispatcher 中判断是否保存 Cache 。主要实现类为 DiskBasedCache ,缓存在磁盘中。
Volley
看完了之后,我们就要开始源码解析。我们入手点就是 Volley.newRequestQueue(context)
了。
1 | public class Volley { |
从上面 Volley 类的源码中可知,Volley 类主要就是用来创建 RequestQueue 的。我们之前使用的 newRequestQueue(Context context)
方法最终会调用 newRequestQueue(Context context, HttpStack stack)
。Volley 允许我们使用自定义的 HttpStack ,从这也可以看出 Volley 具有很强的扩展性。
RequestQueue
接下来继续跟踪 RequestQueue 构造方法的代码。
1 | // 默认线程池数量为 4 |
在构造方法中创建了 ExecutorDelivery 对象,ExecutorDelivery 中传入的 Handler 为主线程的,方便得到 Response 后回调;NetworkDispatcher[] 数组对象,默认数组的长度为 4 ,也就意味着默认处理请求的线程最多为 4 个。
在 Volley.newRequestQueue(Context context, HttpStack stack)
中创建完 RequestQueue 对象 queue
之后,还调用了 queue.start()
方法。主要用于启动 queue
中的 mCacheDispatcher
和 mDispatchers
。
1 | /** 请求缓存队列 */ |
那么看到这里我们意识到有必要看一下 CacheDispatcher 和 NetworkDispatcher 的代码。我们先暂且放一下,来看看 RequestQueue 的 add
方法。add
方法就是把 Request 加入到 RequestQueue 中了:
1 | private final Map<String, Queue<Request<?>>> mWaitingRequests = |
在 add(Request<T> request)
方法中,额外使用了两个集合来维护 Request ,其中
- mCurrentRequests :用来维护正在做请求操作的 Request;
- mWaitingRequests :主要作用是如果当前有一个 Request 正在请求并且是可以缓存的,那么 Volley 会去 mWaitingRequests 中根据该 cacheKey 查询之前有没有一样的 Request 被加入到 mWaitingRequests 中。若有,那么该 Request 就不需要再被缓存了;若没有就加入到 mCacheQueue 中进行后续操作。
现在我们来看看 CacheDispatcher 和 NetworkDispatcher 类的源码。
CacheDispatcher
首先是 CacheDispatcher 的:
1 | public class CacheDispatcher extends Thread { |
CacheDispatcher 类主要的代码就如上面所示了,在主要的 run()
方法中都添加了注释,阅读起来应该比较简单。那么在这里就贡献一张 CacheDispatcher 类的流程图:
NetworkDispatcher
然后是 NetworkDispatcher 的代码:
1 | public class NetworkDispatcher extends Thread { |
同样的,根据 NetworkDispatcher 我们也可以梳理出一张流程图:
Request
到这里,我们把目光转向 Request 。Request 是一个抽象类:
1 | public abstract class Request<T> implements Comparable<Request<T>> { |
Request 实现了 Comparable 接口,这是因为 Request 是有优先级的,优先级高比优先级低的要先响应,排列在前。默认有四种优先级:
1 | public enum Priority { |
另外,子类继承 Request 还要实现两个抽象方法:
- parseNetworkResponse :把 NetworkResponse 转换为合适类型的 Response;
- deliverResponse :把解析出来的类型分发给监听器回调。
另外,Request 还支持八种请求方式:
1 | /** |
在 Volley 中,Request 的子类众多,有 StringRequest 、JsonObjectRequest(继承自 JsonRequest ) 、JsonArrayRequest(继承自 JsonRequest ) 和 ImageRequest 等。当然这些子类并不能满足全部的场景要求,而这就需要我们开发者自己动手去扩展了。
下面我就分析一下 StringRequest 的源码,其他子类的源码都是类似的,可以回去自行研究。
1 | public class StringRequest extends Request<String> { |
我们发现 StringRequest 的源码十分简洁。在 parseNetworkResponse
方法中主要把 response 中的 data 转化为对应的 String 类型。然后回调 Response.success
即可。
看完了 Request 之后,我们来分析一下 Network 。
Network
Network 是一个接口,里面就一个方法 performRequest(Request<?> request)
:
1 | public interface Network { |
光看这个方法的定义就知道这个方法是用来干什么了!就是根据传入的 Request 执行,转换为对应的 NetworkResponse 的,并且该 NetworkResponse 不为空。我们就跳到它的实现类中看看该方法具体是怎么样的。
1 | public class BasicNetwork implements Network { |
我们把 BasicNetwork 的源码全部看下来,发现 BasicNetwork 干的事情就如下:
- 利用 HttpStack 执行请求,把响应 HttpResponse 封装为 NetworkResponse ;
- 如果在这过程中出错,会有重试策略。
至于 NetworkResponse 的源码在这里就不分析了,主要是一个相对于 HttpResponse 的封装类,可以自己去看其源码。
得到 NetworkResponse 之后,在 NetworkDispatcher 中经过 Request 的 parseNetworkResponse
方法把 NetworkResponse 转化为了 Response 。(具体可参考上面分析的 NetworkDispatcher 和 StringRequest 源码)
那么接下来就把目光转向 Response 吧。
Response
Response 类的源码比较简单,一起来看看:
1 | public class Response<T> { |
Response 类主要通过 success
和 error
两个方法分别来构造正确的响应结果和错误的响应结果。另外,在 Response 类中还有 Listener 和 ErrorListener 两个接口。在最终的回调中会使用到它们。
在得到了 Response 之后,就要使用 ResponseDelivery 来分发了。那下面就轮到 ResponseDelivery 了,go on !!
ResponseDelivery
1 | public interface ResponseDelivery { |
ResponseDelivery 的接口就定义了三个方法,我们需要在其实现类中看看具体的实现:
1 | public class ExecutorDelivery implements ResponseDelivery { |
ResponseDelivery 将根据 mResponse 是否成功来调用不同的方法 mRequest.deliverResponse
和 mRequest.deliverError
。在 mRequest.deliverResponse
中会回调 Listener 的 onResponse
方法;而在 mRequest.deliverError
中会回调 ErrorListener 的 onErrorResponse
方法。至此,一个完整的网络请求及响应流程走完了。
HttpStack
现在回过头来看看 Volley 框架中是如何发起网络请求的。在本文的开头中说过,Volley 是会根据 Android 的版本来选择对应的 HttpStack。那么下面我们来深入看一下 HttpStack 的源码。
1 | public interface HttpStack { |
HttpStack 接口中定义的方法就只有一个。我们要分别来看看 HurlStack 和 HttpClientStack 各自的实现。
HurlStack :
1 | @Override |
HttpClientStack :
1 | @Override |
在这里只给出 HurlStack 和 HttpClientStack 的 performRequest
方法。我们可以看到 HurlStack 和 HttpClientStack 已经把 HttpUrlConnection 和 HttpClient 封装得很彻底了,以后哪里有需要的地方可以直接使用。
RetryPolicy
RetryPolicy 接口主要的作用就是定制重试策略,我们从下面的源码可以看出该接口有三个抽象方法:
- getCurrentTimeout :得到当前超时时间;
- getCurrentRetryCount :得到当前重试的次数;
- retry :是否进行重试,其中的
error
参数为异常的信息。若在retry
方法中跑出error
异常,那 Volley 就会停止重试。
1 | public interface RetryPolicy { |
RetryPolicy 接口有一个默认的实现类 DefaultRetryPolicy ,DefaultRetryPolicy 的构造方法有两个:
1 | /** The default socket timeout in milliseconds */ |
从上面可以看到,在 Volley 内部已经有一套默认的参数配置了。当然,你也可以通过自定义的形式来设置重试策略。
1 | @Override |
Cache
分析完了前面这么多的类,终于轮到了最后的 Cache 。Cache 接口中定义了一个内部类 Entry ,还有定义了几个方法:
- get(String key) :根据传入的
key
来获取 entry ; - put(String key, Entry entry) :增加或者替换缓存;
- initialize() :初始化,是耗时的操作,在子线程中调用;
- invalidate(String key, boolean fullExpire) :根据
key
使之对应的缓存失效; - remove(String key) :根据
key
移除某个缓存; - clear() :清空缓存。
1 | public interface Cache { |
内部类 Entry ,Entry 中有一个属性为 etag ,上面的源码中也有 etag 的身影。如果你对 ETag 不熟悉,可以查看这篇文章《Etag与HTTP缓存机制》:
1 | public static class Entry { |
看完了 Cache 接口之后,我们来看一下实现类 DiskBasedCache 。首先是它的构造方法:
1 | private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024; |
从构造方法中传入的参数可知,Volley 默认最大磁盘缓存为 5M 。
DiskBasedCache 的 get(String key)
方法:
1 | @Override |
DiskBasedCache 的 putEntry(String key, CacheHeader entry)
方法:
1 | @Override |
initialize()
方法:
1 | @Override |
invalidate(String key, boolean fullExpire)
方法:
1 | @Override |
remove(String key)
和 clear()
方法比较简单,就不需要注释了:
1 | @Override |
0100B
至此,Volley 源码解析差不多已经结束了。基本上在整个 Volley 框架中至关重要的类都讲到了。当然,还有一些 NetworkImageView 、ImageLoader 等源码还没解析。由于本篇文章内容太长了(有史以来写过最长的一篇─=≡Σ((( つ•̀ω•́)つ),只能等到下次有机会再给大家补上了。
在这还给出了一张整个 Volley 框架大致的网络通信流程图,对上面源码没看懂的童鞋可以参考这张图再看一遍:
最后,只剩下总结了。从头到尾分析了一遍,发现 Volley 真的是一款很优秀的框架,面向接口编程在其中发挥到极致。其中有不少值得我们借鉴的地方,但是 Volley 并不是没有缺点的,对于大文件传输 Volley 就很不擅长,搞不好会 OOM 。另外,在源码中还有不少可以继续优化的地方,有兴趣的同学可以自定义一个属于自己的 Volley 。
好了,如果你对本文哪里有问题或者不懂的地方,欢迎留言一起交流。
0101B
References