珍惜作者劳动成果,如需转载,请注明出处。
http://blog.csdn.net/zhengzechuan91/article/details/50433704
Volley是google在2013年io大会上推荐的网络通信框架,它封装了android中的网络请求模块,使开发者处理网络请求更加简单,适合数据量不大但是通信频繁的场景。
前面我们对Retrofit网络通信框架做了详细的介绍,这篇文章我们就来看看Volley框架是怎么通信的?
首先我们先来看看初始化
/* Volley.java */
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
//默认缓存在cache目录下,文件名为volley
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);
// volley/0/packageName/versionCode
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
//这里处理跟Retrofit一样
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
//request的执行者
Network network = new BasicNetwork(stack);
RequestQueue queue;
if (maxDiskCacheBytes <= -1)
//不设定缓存大小,最大网络线程并发数为4
//并创建ExecutorDelivery将response传递到UI
//线程
queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
}
else
{
//设定缓存大小,最大网络线程并发数为4
//并创建ExecutorDelivery将response传递到UI
//线程
queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
}
//初始化并开启任务队列
queue.start();
return queue;
}
然后我们看看RequestQueue#start()中做了什么,这里有点需要声明下,CacheDispatcher和NetworkDispatcher实际上都是线程,命名为Task会更好些:
/* RequestQueue.java */
public void start() {
//发送中断请求中断所有任务
stop();
//创建缓存任务
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
//开启缓存线程
mCacheDispatcher.start();
//创建4个网络任务并开启
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
//数组是在初始化的时候创建的
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
这个方法很简单,就是停掉之前的所有任务,然后开启新的所有任务。
到了这个时候线程都启动了,我们肯定要去看看run()方法中干了什么,先来看缓存的线程:
/* CacheDispatcher.java */
@Override
public void run() {
//线程优先级为后台级别
Process.setThreadPriority(
Process.THREAD_PRIORITY_BACKGROUND);
//对Volley#newRequestQueue()时产生的
//DiskBasedCache实例初始化,实际上就是扫描根目录,
//将所有的CacheHeader装入到内存(mEntries)中
mCache.initialize();
Request<?> request;
while (true) {
//释放前面的request对象,避免mQueue用完后内存
//泄露
request = null;
try {
//从阻塞队列中取request
request = mCacheQueue.take();
} catch (InterruptedException e) {
//用户发送Thread#interrupt()中断请求退出
//线程
if (mQuit) {
return;
}
//不是用户主动退出继续任务
continue;
}
try {
// 如果调用Request#cancel()取消任务
if (request.isCanceled()) {
//这时候会将request从当前加载的
//request列表中移除,如果添加了
//finish后通知的listener,通知
//listener此request完成,如果
//未完成的request需要记下来,则将等待
//中的request队列mWaitingRequests
//移动到mCacheQueue中
request.finish("cache-discard-canceled");
continue;
}
// 通过key获取磁盘上保存的实体
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
//缓存中找不到,直接将request添加到
//网络队列中
mNetworkQueue.put(request);
continue;
}
// 缓存过期,HttpHeaderParser#
//parseCacheHeaders()这个方法里定义了
//ttl,默认为head头的(Expires-Date)
if (entry.isExpired()) {
//关联request和磁盘上过期的实体
request.setCacheEntry(entry);
//添加到网络队列中
mNetworkQueue.put(request);
continue;
}
//已经命中了磁盘上的缓存,将磁盘上的内容解析
//为Repsonse
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
//默认soft ttl与ttl一样
if (!entry.refreshNeeded()) {
//磁盘上缓存不需要刷新,直接将
//response传递给UI线程
mDelivery.postResponse(request, response);
} else {
//关联request和磁盘上的实体
request.setCacheEntry(entry);
//标注数据会刷新
response.intermediate = true;
//先返回磁盘的缓存,再去网络加载
final Request<?> finalRequest = request;
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(finalRequest);
} catch (InterruptedException e) {
}
}
});
}
} catch (Exception e) {
}
}
让我们总结下缓存任务中做了哪些事情:
1. 如果使用Thread#interrupt()中断任务,则直接退出;
2. 如果Request#cancel()取消掉任务,则会从当前执行的列表和等待的队列中移除掉;
3. 如果缓存中找不到或已过期,直接去网络任务中执行;
4. 如果磁盘上的缓存需要更新,则先加载磁盘上的缓存返回给UI线程,在去网络任务中执行;
5. 如果磁盘上存在并且没有过期,则直接加载磁盘上的缓存,并返回给UI线程。
看完了缓存任务的逻辑,我们再来看看网络任务的逻辑:
/* NetworkDispatcher.java */
@Override
public void run() {
//后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request<?> request;
while (true) {
long startTimeMs =
SystemClock.elapsedRealtime();
//释放前面的request对象,避免mQueue用完后内存
//泄露
request = null;
try {
//从网络队列中去request
request = mQueue.take();
} catch (InterruptedException e) {
// 用户中断,直接退出
if (mQuit) {
return;
}
continue;
}
try {
// 任务被取消,从当前列表和等待队列中移除
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
//4.0以上在线程中标识数据传输
addTrafficStatsTag(request);
// 执行请求任务
NetworkResponse networkResponse = mNetwork.performRequest(request);
//将流数据转换为Response
Response<?> response = request.parseNetworkResponse(networkResponse);
// 写入磁盘缓存
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
}
//将request返回UI线程
request.markDelivered();
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
//将错误结果返回UI线程
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
//将错误结果返回UI线程
mDelivery.postError(request, volleyError);
}
}
}
我们再来总结下网络任务中的逻辑:
1. 如果使用Thread#interrupt()中断任务,则直接退出;
2. 如果Request#cancel()取消掉任务,则会从当前执行的列表和等待的队列中移除掉;
3. 执行网络请求,将NetworkResponse中的流数据包装成Response返回;
4. 写入磁盘缓存;
5. 将返回结果通过ResponseDelivery返回到UI线程。
接下来我们看看任务被添加的代码:
/* RequestQueue.java */
public <T> Request<T> add(Request<T> request) {
//关联request和requestQueue
request.setRequestQueue(this);
//添加到当前请求列表中
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
//对request编号
request.setSequence(getSequenceNumber());
//如果不需要缓存,直接添加到网络队列
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
// 如果等待队列中已经有此request的key,说明之
//前已经添加过
if (mWaitingRequests.containsKey(cacheKey)) {
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
//这次才会添加到等待队列中
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
} else {
//第一次无需将request添加到等待队列中
mWaitingRequests.put(cacheKey, null);
//第一次直接添加到缓存队列中
mCacheQueue.add(request);
}
return request;
}
}
从上面我们看到,RequestQueue中共有四个地方来保存request,mWaitingRequests保存了缓存时每个key对应的request队列,mCurrentRequests保存了当前正在执行的request集合,mCacheQueue是带有优先级的缓存队列,mNetworkQueue是带有优先级的网络队列。
如果不用缓存,那么直接添加到网络队列中就直接返回了。开启的4个执行网络任务的线程都是从mNetworkQueue中取request的。
看完上面的代码,我们一定会有一个疑惑,有任务我们直接添加到缓存队列和网络队列中就行了,为什么还要有当前执行的集合和等待队列呢?
首先我们先看当前执行的request集合mCurrentRequests是做什么用的,mCurrentRequests是在每次add()的时候不管是缓存request还是网络request都会添加进去的,而在finish()时移除的,所以这个集合中保存的是当前正在执行的request,而当我们根据tag使用cancelAll()方法移除request的时候,实际上移除的就是这个集合中正在执行的request,而ExecutorDelivery在返回UI线程时也会先判断这个request是否cancel了。
等待队列mWaitingRequests跟mCurrentRequests的功能相似,不过只有在执行缓存request才会用到。由于缓存任务只有一个线程,同一个request我们可能会多次添加到缓存任务中,如果直接添加到mCacheQueue中,势必会影响我们的效率,这时候我们可以先将其缓存在mWaitingRequests中,当这个request执行完finish()时在将mWaitingRequests中的request队列再全部放到mCacheQueue中执行。
分析完了框架运行的流程,我们看看自定义Request:
系统已经帮我们实现了几个常用的Request,例如StringRequest、JsonRequest等,但是我们可能需要根据自己的需求去自定义自己的Request,Request是一个抽象类,要自定义Request必须实现抽象方法parseNetworkResponse()和deliverResponse()两个方法,前者将NetworkResponse解析为Response,而后者则是将泛型T返回到UI线程。
除了上面提到的,Request里我们还会添加Header,如果是POST/PUT还要添加Body,那这个框架又是怎么实现的呢?
先来看看默认实现:
/* Request.java */
//默认header为空
public Map<String, String> getHeaders() throws AuthFailureError {
return Collections.emptyMap();
}
//默认POST/PUT请求参数为空,如不需要修改contentType则覆写此方法
protected Map<String, String> getParams() throws AuthFailureError {
return null;
}
//默认POST/PUT请求的body为encode getParams()中的参数,如需修改
//contentType则覆写此方法
public byte[] getBody() throws AuthFailureError {
Map<String, String> params = getParams();
if (params != null && params.size() > 0) {
return encodeParameters(params, getParamsEncoding());
}
return null;
}
一般情况下我们会覆写getHeaders()来向header中添加参数,而POST/PUT方法覆写getParams()来向body中写入参数。
上面设置的这些header和body会在构建http连接实例时用到,让我们来看看这部分的逻辑。
/* HttpStack.java */
public interface HttpStack {
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError;
}
封装http连接的接口是HttpStack,返回的Response为HttpResponse,实现类HurlStack封装的是HttpURLConnection的逻辑,将返回值包装成了HttpResponse,而HttpClientStack封装的是HttpClient的逻辑,直接返回的就是HttpResponse。
/* Network.java */
public interface Network {
public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}
Network则是Volley框架请求的接口,实现类BasicNetwork里也只是对HttpStack的包装而已,而且将HttpStack返回的HttpResponse包装成了NetworkResponse。
好了,平常用到的就这么多东西,我们来总结下Volley这个框架:
1. 使用Volley#newRequestQueue()初始化RequestQueue并启动RequestQueue;
2. 开启1个缓存线程CacheDispatcher和4个网络线程CacheDispatcher;
3. 缓存线程从缓存的阻塞队列中取request,根据key从磁盘上读取缓存,读取不到再去添加到网络的阻塞队列;
4. 网络线程从网络的阻塞队列中取request,使用Network发起http网络请求,返回的结果会封装在NetworkResponse中;
5. 根据我们重写的Request的parseNetworkResponse()方法将
NetworkResponse解析为Response;
6. 成功返回时会通过ResponseDelivery#postResponse()使用Handler通知到UI线程,实际上就是在UI线程执行ResponseDeliveryRunnable,在我们重写的Request#deliverResponse()方法中通知我们实例化Request时传入的Listener#onResponse()回掉;
7. 失败时会通过ResponseDelivery#postError()使用Handler通知到UI线程,实际上就是在UI线程执行ResponseDeliveryRunnable,通过Request#deliverError()方法将VolleyError通过我们实例化Request时传入的Response.ErrorListener#onErrorResponse()回调。