手撕 Volley (一)
android_volley_tutorial.jpg
前言
从去年开始使用Volley,到现在一年多了。前几天参加某互联网公司校招被问到Volley相对其他的网络框架有什么优缺点,它分别是如何实现的。当时答得的并不好。所以趁十一假期读一下Volley的源码。
写这篇文章的目的有两个:1. 总结下 Android 网络编程,学习 Volley 设计思想。2. 给正在使用 Volley 但仍然心存疑惑的人一些更深入的解析。
Volley到底是什么
Volley简介
volley 是 Goole I/O 2013上发布的网络通信库,使网络通信更快、更简单、更健壮。
关键词:数据不大但通信频繁
Volley名称的由来: a burst or emission of many things or a large amount at once
Volley提供的功能
- Json,图像等异步下载
- 网络请求的排序(scheduling)
- 网络请求的优先级处理
- 缓存
- 多级别取消请求
- 和 Activity 的生命周期联动(Activity 结束时同时取消所有网络请求)
Volley好在哪
物理质量
- 使用Volley 需要Volley.jar(120k),加上自己的封装最多140k。
- 使用OkHttp需要 okio.jar (80k), okhttp.jar(330k)这2个jar包,总大小差不多400k,加上自己的封装,差不多得410k。
Volley 的优点
- 非常适合进行数据量不大,但通信频繁的网络操作
- 可直接在主线程调用服务端并处理返回结果
- 可以取消请求,容易扩展,面向接口编程
- 网络请求线程NetworkDispatcher默认开启了4个,可以优化,通过手机CPU数量
- 通过使用标准的HTTP缓存机制保持磁盘和内存响应的一致
Volley 的缺点
- 使用的是httpclient、HttpURLConnection
- 6.0不支持httpclient了,如果想支持得添加org.apache.http.legacy.jar
- 对大文件下载 Volley的表现非常糟糕
- 只支持http请求
- 图片加载性能一般
Volley 的使用场景和使用方式
关于 Volley 怎么用网络上的文章太多了,链接整理如下
Android Volley完全解析(一),初识Volley的基本用法
Android Volley完全解析(二),使用Volley加载网络图片
Android Volley完全解析(三),定制自己的Request
Android Volley 之自定义Request
官方教程(需要翻墙)
An Introduction to Volley
Http 权威指南笔记
不是要读 Volley 的源码嘛,怎么又看起了 HTTP 权威指南,原因很简单,Volley 源码里面有很多处理是与 HTTP 协议息息相关的,只有了解了协议才能更深入的理解Volley。Volley里面涉及协议的地方都会在注释中给出协议文档的链接。
Hypertext Transfer Protocol -- HTTP/1.1
HTTP权威指南读书笔记
HTTP协议详解(真的很经典)
HTTP协议详解
这里简单介绍一几个概念:
HTTP.png
- HTTP 协议属于应用层协议,他的基础是 TCP (传输层)/ IP (网络层)协议
- 一个HTTP事务由一条请求命令和一个响应结果组成。这种通信通过名为 HTTP 报文(HTTP message)的格式化数据块进行
- 从Web客户端发往Web服务器的HTTP报文称为请求报文(request message)。从服务器发往客户端的报文称为响应报文(reponse message),请求报文和响应报文格式类似。 HTTP报文包括以下三部分:
-
start line 起始行
*请求报文 包括 method + path + version
*响应报文 包括 version + status line - headers 消息报头,包含了很多键值对,两者之间用冒号(:)分隔。首部以一个空行结束,这里是我们开发主要会用到的地方,特别是缓存。建议大家还是阅读一下连接中给出的相关文章。
- entity / body消息实体,空行之后就是可选的报文主体了,其中包含了所有类型的数据。起始行和首部都是文本形式且都是结构化的,而主体则不同,主体可以包含任意的二进制数据(图片、视频、音频、软件程序)。当然,主体还可以包含文本。
下面给出一组请求和响应的样例。
HTTP_RequestMessageExample.png
HTTP_ResponseMessageExample.png
HttpURLConnection 与 HttpClient
这里了解一下 httpClient 和 HttpURLConnection 的区别和历史,并主要学习一下 HttpURLConnection 的使用,android 社区现在更推荐使用 HttpURLConnection 来进行网络开发。
需要注意的是 android 6.0 SDK,不再提供 org.apache.http 的支持,所以 6.0 以后要想使用 Volley(HttpClient)需要手动配置 gradle 了。
HttpURLConnection(官方文档,需要翻墙)
Interface HttpClient
A Comparison of java.net.URLConnection and HTTPClient
HttpClient和HttpURLConnection的区别
Volley 源码解析
ok 做完了前面的准备工作终于可以开始最激动人心的部分了,首先来一张官方给出的流程图。
volley-request.png对于这张图我们只需要知道:
- Volley 运行的过程中一共有三种线程,包括 UI 线程、Cache 调度线程和 NetWork 调度线程池
- 请求加入优先级队列,Cache 线程进行筛选,如果命中(hit)分发给 UI 线程
- 未命中(miss)交给 NetWork 调度线程池处理,取回后更新 Cache 并分发给 UI 线程
- 每次请求执行过程始于 UI 线程, 终于 UI 线程
再来一张总体设计图:
flow.png
入口
我用的 Sublime Text 3 来阅读 Volley ,可以看到 Volley 中大大小小一共43个类。
我们使用 Volley 的第一步是通过Volley 的 newRequestQueue 方法得到 一个RequestQueue 队列。那么我们就从这个方法开始吧。不管几个参数的 newRequestQueue 方法最终都会调用下面这个三个参数的。
可以看到:
- 在磁盘上创建一块文件
- 设置 UserAgent ,不知道什么是UserAgent去看前面的HTTP协议
- 根据 SDK 版本的不同初始化 HTTPStack
- 用 HTTPStack 初始化 BasicNetwork
- 用第一步创建的文件初始化磁盘缓存
- 用磁盘缓存和 NetWork 创建我们的 请求队列 RequestQueue
- 调用 RequestQueue 的 start 方法
这里面我们有几个疑问
- HttpStack、HurlStack、HttpClientStack 分别是啥
- NetWork、BasicNetwork 分别是啥
- DiskBasedCache 是啥
- RequestQueue 是啥,他的 start 方法做了什么事情
首先看第一个 HttpStack
/**
* An HTTP stack abstraction.
*/
public interface HttpStack {
/**
* Performs an HTTP request with the given parameters.
*/
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError;
}
/**
* An HttpStack that performs request over an {@link HttpClient}.
*/
public class HttpClientStack implements HttpStack {
/**
* An {@link HttpStack} based on {@link HttpURLConnection}.
*/
public class HurlStack implements HttpStack {
HttpStack 类图
HttpStack.png
很明显 HttpStack 是一个接口并且只有一个 performRequest 方法。
而 HurlStack 和 HttpClientStack 分别是基于 HttpUrlConnection 和 HttpClient 对 HttpStack 的实现,是真正用来访问网络的类。内部具体实现后面再看。
下面再来看NetWork 和 BasicNetWork:
/**
* An interface for performing requests.
*/
public interface Network {
/**
* Performs the specified request.
* @param request Request to process
* @return A {@link NetworkResponse} with data and caching metadata; will never be null
* @throws VolleyError on errors
*/
public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}
}
/**
* A network performing Volley requests over an {@link HttpStack}.
*/
public class BasicNetwork implements Network {
protected static final boolean DEBUG = VolleyLog.DEBUG;
private static int SLOW_REQUEST_THRESHOLD_MS = 3000;
private static int DEFAULT_POOL_SIZE = 4096;
protected final HttpStack mHttpStack;
protected final ByteArrayPool mPool;
/**
* @param httpStack HTTP stack to be used
*/
public BasicNetwork(HttpStack httpStack) {
// If a pool isn't passed in, then build a small default pool that will give us a lot of
// benefit and not use too much memory.
this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
}
/**
* @param httpStack HTTP stack to be used
* @param pool a buffer pool that improves GC performance in copy operations
*/
public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
mHttpStack = httpStack;
mPool = pool;
}
}
Network.png
可以看到同样是接口与实现类的关系,内部封装了一个 HttpStack 用来是想网络请求。
接口的方法是一样的,这又是为什么呢,这里暂且不管,后面看实现再分析。
接下来是 DiskBasedCache:
/**
* Cache implementation that caches files directly onto the hard disk in the specified
* directory. The default disk usage size is 5MB, but is configurable.
*/
public class DiskBasedCache implements Cache {
可以看得到 DiskBaseCache 继承自 Cache接口,老规矩我们先看接口不看具体实现,先知道他是干嘛的。
/**
* An interface for a cache keyed by a String with a byte array as data.
*/
public interface Cache {
/**
* an entry from the cache.
* @param key Cache key
* @return An {@link Entry} or null in the event of a cache miss
*/
public Entry get(String key);
public void put(String key, Entry entry);
public void initialize();
public void invalidate(String key, boolean fullExpire);
public void remove(String key);
public void clear();
/**
* Data and metadata for an entry returned by the cache.
*/
public static class Entry {
/** The data returned from cache. */
public byte[] data;
/** ETag for cache coherency. */
public String etag;
/** Date of this response as reported by the server. */
public long serverDate;
/** The last modified date for the requested object. */
public long lastModified;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
/** Immutable response headers as received from server; must be non-null. */
public Map<String, String> responseHeaders = Collections.emptyMap();
/** True if the entry is expired. */
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/** True if a refresh is needed from the original data source. */
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
}
Cache.png
又是接口,Volley 核心功能的实现都是基于接口的。
我们来看,接口 Cache 里面封装了一个静态内部类 Entry(登记),这个内部类非常重要,看了 HTTP 协议的同学们会发现,Entry 里面定义的这些成员变量跟 headers(消息报头)里面关于缓存的标签是一样的,这也是前面强调要看协议的原因。其中还维护了一个map 用来保存消息报头中的 key / value,data 来保存 entity 消息实体。除此之外就是一些集合操作了。
我们使用 Volley 的时候创建一个 request 然后把它丢到 RequestQueue 中就可以了。那么来看 RequestQueue 的构造方法,下面是最终会调用的构造器。
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
设置了几个成员变量,那么 RequestQueue到底有哪些成员变量呢
/** Used for generating monotonically-increasing sequence numbers for requests. */
private AtomicInteger mSequenceGenerator = new AtomicInteger();
/**
* Staging area for requests that already have a duplicate request in flight.
*
* <ul>
* <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache
* key.</li>
* <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request
* is <em>not</em> contained in that list. Is null if no requests are staged.</li>
* </ul>
*/
private final Map<String, Queue<Request<?>>> mWaitingRequests =
new HashMap<String, Queue<Request<?>>>();
/**
* The set of all requests currently being processed by this RequestQueue. A Request
* will be in this set if it is waiting in any queue or currently being processed by
* any dispatcher.
*/
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
/** The cache triage queue. */
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
/** The queue of requests that are actually going out to the network. */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
/** Number of network request dispatcher threads to start. */
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
/** Cache interface for retrieving and storing responses. */
private final Cache mCache;
/** Network interface for performing requests. */
private final Network mNetwork;
/** Response delivery mechanism. */
private final ResponseDelivery mDelivery;
/** The network dispatchers. */
private NetworkDispatcher[] mDispatchers;
/** The cache dispatcher. */
private CacheDispatcher mCacheDispatcher;
private List<RequestFinishedListener> mFinishedListeners =
new ArrayList<RequestFinishedListener>();
所有的成员变量以及核心方法类图如下,为了直观方法没有加参数:
RequestQueue.png
- mSequenceGenerator:序列号生成器
- mWaitingRequests:hashmap 通过 method + url 为key,重复 request 组成的 queue 为value
- mCurrentRequests:HashSet 存储包括正在执行和等待所有的 request
- mCacheQueue:PriorityBlockingQueue 缓存队列
- mNetworkQueue:PriorityBlockingQueue 网络请求队列
- DEFAULT_NETWORK_THREAD_POOL_SIZE 网络请求线程池大小
- mCache 接口 具体实现由构造器传入
- mNetwork 同上
- mDelivery 结果分发器
- mDispatchers 网络调度数组
- mCacheDispatcher 缓存调度
RequestQueue 中一共有五个主要的方法,分别是 start、add、stop、cancel、finish
我们先看 刚才遇到的 start 方法中都做了些什么
/**
* Starts the dispatchers in this queue.
*/
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
先调用了 stop,然后分别调用了dispatcher 的 start
/**
* Stops the cache and network dispatchers.
*/
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
stop 调用了分别调用了 dispatcher 的quit
那么疑问来了,dispatcher 的 quit 和 start 是干嘛呢
public class CacheDispatcher extends Thread {
public class NetworkDispatcher extends Thread {
CacheDispatcher 和 NetworkDispatcher 都继承自Thread,start 方法自然是开启一个新的线程那quit,一定是关闭线程了,看一下 Volley 是怎么实现的
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
while (true) {
if (mQuit) {
return;
}
}
}
我们忽略具体实现可以看到,run 方法里面是一个 while true 的无限循环,然后用以个标记字段,来控制循环退出。
所以 start 方法做的的事情就很清楚了,先 stop 掉跑着的线程,然后开启一个缓存线程, 一组(默认四个)网络线程,每个里面都有一个while ture 死循环。等待 request add 到 Requestqueue 中,接下来我们就来看五个主要方法中的 add
手撕 Volley(二)