Volley 是 Google 官方推出的一套 Android 网络请求库,特别适用于通信频繁、数据量较小的网络请求。Volley 能够根据当前手机版本选择 HttpClient (2.3 以下) 或者 HttpUrlConnection。
除了 Volley,Android 常用的网络加载库还有 OkHttp,Retrofit 等,关于这几个的区别请移步 stormzhong 的ANDROID开源项目推荐之「网络请求哪家强]
Volley 框架扩展性很强,其源码值得我们好好学习。
先了解一些 Http 协议
Http 协议规范了客户端和服务端数据请求的一套规则。Http 协议规范了 请求(Request)和响应(Response)。
请求包含请求行,请求头(Header),body(可选)。如下图所示(图片来源于网络):
请求行必须指定请求方式,常见的有 GET,POST等。
响应和请求类似。也包含三部分,状态行(含响应码),响应头(Header),body(可选)。如下图所示(图片来源于网络):
关于 Http 协议大致了解这些即可。
Volley 对 Http Request 和 Response 的封装。
请求封装
Http 请求包含三部分,请求行,请求头,body。Volley 将其封装成 Request,Request 有几个实现类,如 StringRequest,JsonObjectRequest 等。Request 部分源码如下:
public abstract class Request implements Comparable> {
public interface Method {
int DEPRECATED_GET_OR_POST = -1;
int GET = 0;
int POST = 1;
int PUT = 2;
int DELETE = 3;
int HEAD = 4;
int OPTIONS = 5;
int TRACE = 6;
int PATCH = 7;
}
// 对应请求头中的 Method
private final int mMethod;
// 对应请求头中的 URL
private final String mUrl;
// 对应请求头
public Map getHeaders() throws AuthFailureError {
return Collections.emptyMap();
}
// 对应请求实体
public byte[] getBody() throws AuthFailureError {
Map params = getParams();
if (params != null && params.size() > 0) {
return encodeParameters(params, getParamsEncoding());
}
return null;
}
}
响应封装
Http 响应包含三部分,状态行,响应头,body。
Volley 对 Http 响应用 NetworkResponse 封装,NetworkResponse 部分源码如下:
public class NetworkResponse {
/** 状态行中的响应码 */
public final int statusCode;
/** body */
public final byte[] data;
/** 响应头 */
public final Map headers;
/** True if the server returned a 304 (Not Modified). */
public final boolean notModified;
/** Network roundtrip time in milliseconds. */
public final long networkTimeMs;
}
NetworkResponse 直观的表示 Http 响应返回的数据。但是我们的应用程序不能直接使用其中的 body(字节数组),需要解析成某个 Bean 对象,这样程序就可以直接使用 Bean 对象。因此还需要对响应做进一步的封装。显然这个 Bean 的数据类型不可知,可以使用 java 中的泛型。
Volley 对响应进一步封装成 Response,Response 部分源码码如下:
public class Response {
/** Parsed response, or null in the case of error. */
public final T result;
/** Cache metadata for this response, or null in the case of error. */
public final Cache.Entry cacheEntry;
/** Detailed error information if errorCode != OK
. */
public final VolleyError error;
}
由 NetworkResponse 到 Response 需要一个方法将 body 解析成某个对象。Volley 将这个方法定义在 Request 中,而且是一个抽象方法。看下 StringRequest 对这个方法的实现:
@Override
protected Response 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));
}
其实就是对返回的字节数组(body)解析成某个对象,StringRequest 则解析成 String,JsonObjectRequest 则解析成 JsonObject 等。我们可以定义一个自己的 Request,在 parseNetworkResponse 方法中将字节数组转成 String,再用 Gson 解析成我们的对象。
发起一个请求
Volley 的简单使用如下:
RequestQueue mQueue = Volley.newRequestQueue(context);
StringRequest stringRequest = new StringRequest("http://www.baidu.com",
new Response.Listener() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(stringRequest);
Volley.newRequestQueue(context) 会创建一个 RequestQueue,并调用RequestQueue #start()
。
RequestQueue 表示请求队列,是 Volley 框架的核心类。RequestQueue 包含一个网络队列 mNetworkQueue 和一个缓存队列 mCacheQueue ,作为其成员变量。
RequestQueue 部分源码如下:
public class RequestQueue {
/** 缓存队列 */
private final PriorityBlockingQueue> mCacheQueue =
new PriorityBlockingQueue>();
/** 网络队列 */
private final PriorityBlockingQueue> mNetworkQueue =
new PriorityBlockingQueue>();
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();
}
}
}
在 start 方法中,开启一个缓存调度线程 CacheDispatcher,用来处理缓存。默认开启四个网络调度线程 NetworkDispatcher,用来处理网络请求。CacheDispatcher 不断的从 mCacheQueue 中取走 Request 并进行分发处理。NetworkDispatcher 不断的从 mNetworkQueue 取走 Request 并进行分发处理。如果队列为空,则阻塞等等,这些线程都是常驻内存随时待命的。显然一个程序中最好只能有一个 RequestQueue,如果采用多个,应该在适当的时候调用 RequestQueue#stop()
销毁线程释放内存。
当我们向 RequestQueue 添加一个 Request 时,如果 Request 可缓存则添加到 mCacheQueue ,否则添加到 mNetworkQueue 。CacheDispatcher 拿到这个 Request,如果缓存存在并且还没过期,则解析数据并提及给主线程,否则添加到 mNetworkQueue 交给 NetworkDispatcher 处理。流程如下:
缓存如何处理?
app 网络数据缓存
Cache 的实现类为 DiskBasedCache。DiskBasedCache 采用磁盘缓存和内存缓存,但两者缓存的数据不一样。内存缓存只缓存 CacheHeader,而磁盘缓存的是 Entry,只不过是将 Entry 中的数据按一定规则写到文件中,读取缓存时再按照同样的规则读取到 Entry 中。另外,Entry 比 CacheHeader 多了一个字节数组,显然这是比较占内存的,因此内存缓存并没有缓存 Entry。
当缓存满了之后如何处理呢?
DiskBasedCache 中有个方法是pruneIfNeeded(int neededSpace)
,每次执行 put 的时都会先调用该方法。这个方法就会删除较早的缓存。内存缓存保存在 mEntries 中。我们看下这个成员变量:
/** Map of the Key, CacheHeader pairs */
private final Map mEntries =
new LinkedHashMap(16, .75f, true);
LinkedHashMap 有个构造函数为 LinkedHashMap( int initialCapacity, float loadFactor, boolean accessOrder)
最后一个参数 accessOrder 就表示是否按照访问顺序排列。当 accessOrder 为 true,最后执行 get 或者 put 的元素会在 LinkedHashMap 的尾部。
这样 pruneIfNeeded 方法就很容易找到较早的缓存并将其删除。
服务端缓存
当客户端发起一个 Http 请求,如果服务端返回 304,表示请求的资源缓存仍能有效。这样就能减少数据传输。当然,这种方式需要客户端携带额外的头信息,Volley 已经帮我们做了这部分。直接看相应的源码:
public class BasicNetwork implements Network {
@Override
public NetworkResponse performRequest(Request> request) throws VolleyError {
// 省略...
Map headers = new HashMap();
addCacheHeaders(headers, request.getCacheEntry());
// before request
httpResponse = mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// Http 304
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
}
}
private void addCacheHeaders(Map headers, Cache.Entry entry) {
// If there's no cache entry, we're done.
if (entry == null) {
return;
}
if (entry.etag != null) {
headers.put("If-None-Match", entry.etag);
}
if (entry.lastModified > 0) {
Date refTime = new Date(entry.lastModified);
headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
}
}
}
从上可以看到 Volley 在请求头添加了一个 etag 和 lastModified,这些数据来自上次请求的响应头中,Volley对其做了缓存,并且在下一次请求时添加到请求头中。这样服务端就能比较客户端发送的 etag 和自己的 etag,如果相等,说明请求的资源未发生变化,服务端返回304。客户端则对响应码做判断,如果为 304,说明本地缓存有效。
主线程回调
CacheDispatcher 和 NetworkDispatcher 都是运行在非主线程当中的,而我们的 UI 必须在主线程中更新。Volley 采用的是 Handler 来通知主线程更新 UI。
Request 中有一个 deliverResponse 和 deliverError,一个是成功回调,另一个是失败回调。那么这两个方法是什么时候被执行的呢?
看下 NetworkDispatcher 部分源码(省略部分代码)
public class NetworkDispatcher extends Thread {
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
try {
request = mQueue.take();
} catch (InterruptedException e) {
}
try{
// 发起网络请求
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);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
// 分发失败
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
// 分发失败
mDelivery.postError(request, volleyError);
}
}
}
}
和主线程相关的就是 mDelivery.postResponse(request, response)
; 和 mDelivery.postError(request, volleyError)
。
我们看下 NetworkDispatcher 中 mDelivery 是如何创建的。看下 NetworkDispatcher 源码发现是在构造函数,于是去 RequestQueue 找 NetworkDispatcher 对象的创建过程。在 start 方法中会创建 NetworkDispatcher ,并传入一个 mDelivery 对象,而 mDelivery 在 RequestQueue 的构造函数中已经完成了初始化,看下相关源码:
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
由此可见在 NetworkDispatcher 中 mDelivery 的实际类型是 ExecutorDelivery。ExecutorDelivery 的构造函数接收一个 Handler 用来往主线程发消息。
看下 ExecutorDelivery 部分源码:
public class ExecutorDelivery implements ResponseDelivery {
/** Used for posting responses, typically to the main thread. */
private final Executor mResponsePoster;
public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
@Override
public void postResponse(Request> request, Response> response) {
postResponse(request, response, null);
}
@Override
public void postResponse(Request> request, Response> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
@Override
public void postError(Request> request, VolleyError error) {
request.addMarker("post-error");
Response> response = Response.error(error);
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
}
}
postResponse 和 postError 最终都会提交 一个 Runnable 给 mResponsePoster 执行,而 mResponsePoster 则将这个 Runnable 提交给 Handler 去执行。Handler 接收到 Runnable 之后最终会执行 mRequest.deliverResponse(mResponse.result)
或者 mRequest.deliverError(mResponse.error)
完成主线程的回调。
小结
关于 Volley 的源码大致先解读这些,重点在于理顺整个逻辑,其他的像 HttpClient,HttpUrlConnection 的网络操作建议直接阅读源码。