Volley
在 retrofit+okhttp 大行其道的今天,volley 已经略显过时。使用 volley,我们无法避免写一些样板代码,但在它刚出现时,曾经很大程度降低了 android 开发中网络请求这部分的难度,简洁、轻量、容易定制扩展,我们依然能从它的源码中感受到这些。
看一个使用 volley 的小 demo
StringRequest request = new StringRequest("http://example.com", new Response
.Listener<String>() {
@Override
public void onResponse(String s) {
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
}
});
RequestQueue requestQueue = Volley.newRequestQueue(context);
requestQueue.add(request);
三步走
创建一个 Request
创建一个 RequestQueue
联结两者
这就是 volley 作为一个网络请求库提供给我们的用户界面,超级简洁有没有。Request 是一个数据,是一个相对静态的概念,我们使用它来说我们的网络请求要发送到哪里、带有什么样的参数、返回的数据是什么对象、请求成功了怎样处理、请求失败了又要怎么办,等等等等。RequestQueue 呢,是我们的执行引擎,吃掉一个个 Request,消费 Request 的属性,同时返回 Response。
我们按照上面的思路,从 Request 和 RequestQueue 作为出发点,由浅入深来阅读 volley 源码。
Request
前面说到,Request 在 volley 中是相对静态的概念,静态是好的,我们喜欢静态的东西,所以我们从 Request 开始。看 Request 的定义
public abstract class Request<T> implements Comparable<Request<T>> {
}
我们看到,Request 是个抽象类,有泛型,实现了 Comparable 接口。看到这里你也许可以大胆推测,这个 Request 是可以大大扩展的,大多数网络请求以表单的方式作为参数,十分多变,那么这个泛型来指名返回结果的类型应该是十分合理的。没错,就是这样。实现 Comparable 呢,Request 的比较有什么含义?想到上面有 Queue 的概念,这个 Comaparable 也许和请求的优先级有关,对的,事实上 RequestQueue 中的确使用了 PriorityBlockingQueue 来达到管理 Request 优先级的效果,后文再细说。
上面简单的一行代码已经向我们透漏了大量信息,让我们更近一步。看看 Request 有哪些属性
public abstract class Request<T> implements Comparable<Request<T>> {
/**
* Request method of this request. Currently supports GET, POST, PUT, DELETE, HEAD, OPTIONS,
* TRACE, and PATCH.
*/
private final int mMethod;
/** URL of this request. */
private final String mUrl;
/** Default tag for {@link TrafficStats}. */
private final int mDefaultTrafficStatsTag;
/** Listener interface for errors. */
private final Response.ErrorListener mErrorListener;
/** Sequence number of this request, used to enforce FIFO ordering. */
private Integer mSequence;
/** The request queue this request is associated with. */
private RequestQueue mRequestQueue;
/** Whether or not responses to this request should be cached. */
private boolean mShouldCache = true;
/** Whether or not this request has been canceled. */
private boolean mCanceled = false;
/** Whether or not a response has been delivered for this request yet. */
private boolean mResponseDelivered = false;
/** Whether the request should be retried in the event of an HTTP 5xx (server) error. */
private boolean mShouldRetryServerErrors = false;
/** The retry policy for this request. */
private RetryPolicy mRetryPolicy;
/**
* When a request can be retrieved from cache but must be refreshed from
* the network, the cache entry will be stored here so that in the event of
* a "Not Modified" response, we can be sure it hasn't been evicted from cache.
*/
private Cache.Entry mCacheEntry = null;
/** An opaque token tagging this request; used for bulk cancellation. */
private Object mTag;
}
我们简单的分下类:
mMethod、mUrl:用什么 Http 方法给谁发请求,mUrl 中可以附带参数(如果想在 body 中传递参数呢,想定制 Header 呢)
mShouldCache、mCanceled、mCacheEntry:缓存相关
mRequestQueue:关联的 RequestQueue,结束 Request 时使用
mErrorListener:错误回调,想想上面的 demo,还有一个成功的回调,但这里并没有,下文再说
mShouldRetryServerErrors、mRetryPolicy:控制重试
mTag:不同的 Request 可以设置相关的 tag,这样我们可以用 tag 来批量取消 Request
mSequence、mDefaultTrafficStatsTag:忽略
想一想当我们自定义一个 Request 的时候会关心那些属性呢,Method、Url、Cache、ErrorListener,大概就这样吧。
在上面的分类中还有一些问题要解决,一个一个来。
Body
public byte[] getBody() throws AuthFailureError {
Map<String, String> params = getParams();
if (params != null && params.size() > 0) {
return encodeParameters(params, getParamsEncoding());
}
return null;
}
这是个 public 方法,我们可以 override 它。不急,可以看到它使用了 getParams()、encodeParameters()、getParamsEncoding() 方法,我们 override 这三个方法也可以达到间接影响 Body 的效果,encodeParameters() 是 private 的,还好一般没有改变它的必要。
Header
public Map<String, String> getHeaders() throws AuthFailureError {
return Collections.emptyMap();
}
override!
请求成功的回调
abstract protected void deliverResponse(T response);
另外一个比较重要的方法
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
将 NetworkResponse 转化为客户端的类型,这是一个 abstract 方法,我们自定义的 Request 子类必须实现。事实上,demo 中的 StringRequest 就是 volley 中的一个 Request 的具体实现,我们可以从中学习:
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
可以看到,Request 定义了一个网络请求的细节问题,服务、参数、重试策略、缓存策略、返回数据处理等。其中 Header、参数、返回值类型的变化可以通过继承 Request,实现不同的子类来实现。在 volley 中内置了一些实现,包括 StringRequest、JsonObjectRequest、JsonArrayRequest、ImageRequest等,继承的方式显得不太灵活,需要定义一堆的 Request。我们可以稍作扩展,用组合来避免 Request 类的爆炸。
public interface ResponseParser<T> {
Response<T> parseNetworkResponse(NetworkResponse networkResponse);
}
public class MyRequest<T> extends Request<T> {
final private Map<String,String> params;
final private Map<String,String> headers;
final private ResponseParser<T> responseParser;
final private Response.Listener<T> listener;
public MyRequest(int method,
String url,
Response.ErrorListener errorListener,
Map<String,String> params,
Map<String, String> headers,
ResponseParser<T> responseParser,
Response.Listener<T> listener) {
super(method, url, errorListener);
this.params = params;
this.headers = headers;
this.responseParser = responseParser;
this.listener = listener;
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse networkResponse) {
return responseParser.parseNetworkResponse(networkResponse);
}
@Override
protected void deliverResponse(T t) {
listener.onResponse(t);
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
return headers;
}
@Override
protected Map<String, String> getParams() throws AuthFailureError {
return params;
}
}
在使用 volley 的过程中,我们大部分时间会是处理 Request 相关的内容。将 Request 设计的简单直白,降低使用者的难度,这是 volley 非常友好的地方,同时,Volley 的 Request 创建过程也许并不是太有趣,比如请求参数的创建,无聊且不太直观。根据具体的业务,写一些 Builder 可以一定程度弥补这一点。接下来是 RequestQueue 的部分,RequestQueue 在逻辑上是动态的,是一台机器运转的部分,稍微复杂一些,但是,在某种意义上它又是稳定的,它提供了很好的默认实现,多数情况下我们不需要定制,不需要扩展,不需要理解具体细节,它在那里,它完美的 work。
RequestQueue
RequestQueue 这台机器是如何运转的呢?使用 add 方法,一个 Request 首先会被放入 mCacheQueue,mCacheQueue 是一个 PriorityBlockingQueue,CacheDispatcher 从 mCacheQueue 中取出 Request,查看 cache 中是否已经有相同之前相同的请求得到的有效缓存,有就直接使用 ResponseDelivery 将缓存的结果分发到 Request 中的 deliverResponse 方法,从而传递到用户手中;如果没有有效的缓存结果,则将 Request 添加到 mNetworkQueue,NetworkDispatcher 从 mNetworkQueue 中读取 Request,使用 Network 执行 Request 中的请求,成功后将结果放入 Cache,同时用 ResponseDelivery 分发结果。这就是一个 Request 的周期。
流程图如下:
上面我们提到了一些关键的类或属性。
mCacheQueue、mNetworkQueue:两个都是 PriorityBlockingQueue,结合 Request 实现的 Comparable 接口,Request 的优先级在这里实现。
CacheDispatcher、NetworkDispatcher:负责从 Request 到 Response 的转化
Cache、Network:将 Request 转化为 Response 的实际执行者
ResponseDelivery:分发 Response
有了这些概念后,我们分别分析这些类的代码,试着由低向上来理解 RequestQueue。
CacheDispatcher
public class CacheDispatcher extends Thread {
public CacheDispatcher(
BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.
final Request<?> request = mCacheQueue.take();
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
mNetworkQueue.put(request);
continue;
}
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
mDelivery.postResponse(request, response);
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
}
}
}
为便于理解,删去了大量代码,只保留下核心部分。
在构造方法中,我们看到了 mCacheQueue,Request 在 RequestQueue 类中会先放到这里;还有mNetworkQueue,mCacheQueue 中的 Request 没有命中 Cache,会被再放到这里;mCache,存取 Response 的地方;mDelivery,Response 被分发的地方。
CacheDispatcher 继承了 Thread,我们可以 quit 它,看到 quit 中先标记,然后 interrupt,意味着 run 方法中会相应的处理。
run 中,在线程中开启了无限循环,每次循环从 mCacheQueue 读取 Request,然后进行 1 中提到的过程。也可以看到对于 quit 的处理。
NetworkDispatcher
public class NetworkDispatcher extends Thread {
public NetworkDispatcher(BlockingQueue<Request<?>> queue,
Network network, Cache cache,
ResponseDelivery delivery) {
mQueue = queue;
mNetwork = network;
mCache = cache;
mDelivery = delivery;
}
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
while (true) {
Request<?> request;
try {
// Take a request from the queue.
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
NetworkResponse networkResponse = mNetwork.performRequest(request);
Response<?> response = request.parseNetworkResponse(networkResponse);.
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
}
mDelivery.postResponse(request, response);
}
}
}
NetworkDispatcher 和 CacheDispatcher 是不是非常相似?
Cache
一个接口类,和普通的 Cache 没有什么太大差别,不赘述。
Network
public interface Network {
public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}
public interface HttpStack {
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError;
}
它的实现是 BasicNetwork,BasicNetwork 依赖 HttpStack,HttpStack 是实际发起网络请求的地方,volley 分别提供了 HttpClient 和 HttpURLConnection 的实现。我们可以定制这部分,比如基于 OKHttp 的实现。在最后看 Volley 类源码时我们可以看到这个过程。
ResponseDelivery
ResponseDelivery 负责分发 Response,对 Android 来说,就是通过 Handler 将 Response 发送到了主线程
public class ExecutorDelivery implements ResponseDelivery {
private final Executor mResponsePoster;
public ExecutorDelivery(final Handler handler) {
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
}
在 ResponseDeliveryRunnable 中调用了 Request.deliverResponse。
RequestQueue
有了上面的内容,理解 RequestQueue 就很容易了。首先看构造方法。
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
一共四个参数。
cache:一个实现了读写删功能的 Cache 对象,通过它我们可以实现自己的缓存策略。
Network:前面提到,这是一个接口,只有一个要实现的方法,传入一个 Request,传出一个 Response。或许我们也可以把缓存策略放到这里面来,让 Cache 对 RequestQueue 透明,也未尝不是一个不错的想法。
threadPoolSize:NetworkDispatcher 的个数,决定了网络请求的最大并发数。
ResponseDelivery:分发 Response,默认实现 ExecutorDelivery 将 Response 分发到 Request.deliverResponse, 这就是我们 override deliverResponse 方法可以得到 Response 的原因,同时我们从上面 ExecutorDelivery 的代码中看到,这个分发是通过像一个 Handler post 一个 Runnable 实现的,Handler 的性质决定了数据分发到哪个线程。
下面看 start 方法
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();
}
}
mCacheDispatcher、mDispatchers 初始化,从上面我们知道它们都是 Thread 的子类, start 后内部开始循环,从 mCacheQueue、mNetworkQueue 读取 Request 进行处理。
接下来是最重要的方法 add。
public <T> Request<T> add(Request<T> request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
和我们开始讨论 RequestQueue 时的流程图多了一些细节的处理,比如如果一个 Request 被设置为不可缓存,则直接添加到 mNetworkQueue;有相等的 Request 时,会把重复的请求加到一个等待队列,分享请求结果。
RequestQueue 的内容就到这里了。RequestQueue 在并发上没有选择使用 Java 提供的线程池,而是使用固定数据的线程数组(NetworkDispatcher)配合阻塞的优先级队列来实现任务并发。同时在缓存、网络、分发方式上提供了可定制的接口,当然,多数时候使用默认实现就可以了。为了方便的使用默认的实现,就像文章开始的 demo 展示的那样,volley 提供了一个工具类 Volley。
Volley
Volley 只提供两个方法。用来创建默认的 RequestQueue,如下:
public class Volley {
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
}
扩展
Volley 自带的 NetworkImageView
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。