Android(安卓)网络框架 Volley 源码解析
Volley 是 Google 官方推出的一套 Android 网络请求库,特别适用于通信频繁、数据量较小的网络请求。Volley 能够根据当前手机版本选择 HttpClient (2.3 以下) 或者 HttpUrlConnection。
除了 Volley,Android 常用的网络加载库还有 OkHttp,Retrofit 等,关于这几个的区别请移步 stormzhong 的ANDROID开源项目推荐之「网络请求哪家强]
Volley 框架扩展性很强,其源码值得我们好好学习。
先了解一些 Http 协议
Http 协议规范了客户端和服务端数据请求的一套规则。Http 协议规范了 请求(Request)和响应(Response)。
请求包含请求行,请求头(Header),body(可选)。如下图所示(图片来源于网络):
Http Request
请求行必须指定请求方式,常见的有 GET,POST等。
响应和请求类似。也包含三部分,状态行(含响应码),响应头(Header),body(可选)。如下图所示(图片来源于网络):
Http Response
关于 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 的网络操作建议直接阅读源码。
更多相关文章
- Android(安卓)ImageLoader(Android-Universal-Image-Loader)【1】
- Android开发–Intent-filter属性详解
- Android以请求参数来获取网络数据
- 使用RxAndroid处理异步任务
- Android调用WebService系列之请求调用
- Android异步加载访问网络图片-解析json
- fiddle android
- android中OkHttp的导入和get、post请求的简单教程
- Android——Retrofit2