重温Volley源码(一):工作流程
来源:互联网 发布:网络日语翻译兼职 编辑:程序博客网 时间:2024/06/05 06:31
一、写在前面
Volley是Google在2013年I/O大会上推出的Android异步网络请求框架和图片加载框架,新技术的日新月异发展,感觉已经慢慢被OkHttp替代了,现在重新去读它的源码,虽然稍显得有些过气,但还是有很大的学习价值的,在此记录下自己的脚印。
先来看看关于Volley的几个关键特点:
- 适合数据量小,通信频繁的网络操作
- 基于接口设计,面向接口编程,可扩展性强
- 一定程度符合Http规范(ResponseCode请求响应码、请求头处理、缓存机制、请求重试、优先级定义)
- Android 2.2以下基于HttpClient,2.3及以上基于HttpUrlConnenction
- 提供了简便的图片加载工具
二、工作流程
既然是探索Volley的工作流程,我们可以一步步追踪其源码,先来看一个典型的发送Volley网络请求的用法:
//1、创建请求队列 RequestQueue mQueue = Volley.newRequestQueue(this); //2、创建一个网络请求 StringRequest stringRequest = new StringRequest("https://www.baidu.com", new Response.Listener<String>() { @Override public void onResponse(String response) { Log.i(TAG, response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.i(TAG, error.getMessage(), error); } }); //3、将网络请求添加到请求队列中 mQueue.add(stringRequest);
简简单单的三个步骤,完成了一个网络请求,并在onResponse中返回。那么Volley里面具体帮我们干了哪些事情呢,我们进入Volley.newRequestQueue(this)
这个方法内部一探究竟:
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) { 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; if (maxDiskCacheBytes <= -1) { // No maximum size specified queue = new RequestQueue(new DiskBasedCache(cacheDir), network); } else { // Disk cache size specified queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network); } queue.start(); return queue; }
- 如果HttpStack参数为null,则根据系统在API Level>=9采用HurlStack(内部为HttpUrlConnection),如果<9,采用基于HttpClientStack
- 根据HttpStack 创建一个NetWork的具体实现类BasicNetwork对象
- 根据DiskBasedCache磁盘缓存对象、network对象构建一个RequestQueue,调用RequestQueue的start方法启动。
通过源码可以看出,我们可以抛开Volley工具类构建自定义的RequestQueue,采用自定义的HttpStack,采用自定义的NetWork实现,采用自定义的Cache实现来构建RequestQueue,Volley的面向接口编程,高可拓展性的魅力就源于此。
关于HttpURLConnection 和 HttpClient的选择及原因
1. 在Android2.2之前,HttpURLConnection 有个重大的bug,调用 close() 函数会影响连接池,导致连接复用失效,所以在Android2.2之前使用 HttpURLConnection 需要关闭 keepAlive
2. 在Android2.3,HttpURLConnection 默认开启了 gzip 压缩,提高了 HTTPS 的性能;在Android 4.0,HttpURLConnection 支持了请求结果缓存HttpURLConnection 本身 API 相对简单,所以对 Android 来说,在2.3之前建议使用HttpClient,之后建议使用 HttpURLConnection。
本着对Volley请求执行流程的侧重把握,我们接着看RequestQueue的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(); } }
- 调用stop方法,停止所有的线程(CacheDispatcher 和 NetworkDispatcher)
- 创建一个缓存调度线程CacheDispatcher并启动
- 创建n个网络调度线程NetworkDispatcher并启动
在这段start方法执行完后,Volley就已经默认创建了5个线程(1个CacheDispatcher+4个NetworkDispatcher),这里存在优化的余地,比如我们可以根据CPU核数以及网络类型计算更合适的并发数
start方法执行完,由此得到RequestQueue,我们只需要构建相应的Request,然后调用RequstQueue的add方法,就可以完成网络请求操作。
关于Request类
- Request是一个网络请求的抽象类,非抽象子类有StringRequest、JsonRequest、ImageRequest或者自定义子类,我们通过构建这个对象,将其加入RequestQueue来完成一次网络请求操作
- Request子类必须实现的方法有两个:parseNetworkResponse 和 deliverResponse
Volley支持8中请求方式:GET、POST、PUT、DELETE、HEAD、OPTIONS、TRACE、PATCH- Request类包含了请求URL,请求方式,请求Header,请求Body,请求优先级等信息
RequestQueue的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; } }
- 判断是否可以缓存,如果不能缓存则直接加入网络请求队列mNetworkQueue,能缓存则只需执行加入到缓存队列mCacheQueue中
- 默认情况下,Volley的每条请求都是可以缓存的,如果不需要缓存,可以调用Request的setShouldCache(false)方法来改变这一默认行为
既然将请求加入到mNetworkQueue或者mCacheQueue中,接下来就是在对应的NetworkDispatcher或CacheDispatcher线程中执行了。
这里只看NetworkDispatcher的run方法(CacheDispatcher的run方法后半部分和这里类似)
public class NetworkDispatcher extends Thread { ..... @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Request<?> request; while (true) { long startTimeMs = SystemClock.elapsedRealtime(); // release previous request object to avoid leaking request object when mQueue is drained. request = null; 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; } try { request.addMarker("network-queue-take"); // If the request was cancelled already, do not perform the // network request. if (request.isCanceled()) { request.finish("network-discard-cancelled"); continue; } addTrafficStatsTag(request); // Perform the network request. NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete"); // If the server returned 304 AND we delivered a response already, // we're done -- don't deliver a second identical response. if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); continue; } // Parse the response here on the worker thread. Response<?> response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); // Write to cache if applicable. // TODO: Only update cache metadata instead of entire record for 304s. if (request.shouldCache() && response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } // Post the response back. request.markDelivered(); mDelivery.postResponse(request, response); } catch (VolleyError volleyError) { volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); parseAndDeliverNetworkError(request, volleyError); } catch (Exception e) { VolleyLog.e(e, "Unhandled exception %s", e.toString()); VolleyError volleyError = new VolleyError(e); volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); mDelivery.postError(request, volleyError); } } } }
- 外部while(true)的死循环,说明网络线程始终运行
- 调用mNetwork的performRequest方法,将request对象传进入,执行具体的网络请求
- 根据请求的返回值,调用Request的parseNetworkResponse方法来解析NetworkResponse的数据,以及将数据写入到缓存,这个方法的实现是交给Request的子类来完成的,因为不同种类的Request解析的方式也不同,就像在自定义Request中,必须重写parseNetworkResponse方法一样。
在解析完NetworkResponse的数据后,紧接着会调用ResponseDelivery的实现子类ExecutorDelivery的postResponse方法来回调解析出的数据:
@Override public void postResponse(Request<?> request, Response<?> response, Runnable runnable) { request.markDelivered(); request.addMarker("post-response"); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); }
mResponsePoster的execute方法传入了一个ResponseDeliveryRunnable对象:
private class ResponseDeliveryRunnable implements Runnable { private final Request mRequest; private final Response mResponse; private final Runnable mRunnable; public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { mRequest = request; mResponse = response; mRunnable = runnable; } @SuppressWarnings("unchecked") @Override public void run() { // If this request has canceled, finish it and don't deliver. if (mRequest.isCanceled()) { mRequest.finish("canceled-at-delivery"); return; } // Deliver a normal response or error, depending. if (mResponse.isSuccess()) { mRequest.deliverResponse(mResponse.result); } else { mRequest.deliverError(mResponse.error); } // If this is an intermediate response, add a marker, otherwise we're done // and the request can be finished. if (mResponse.intermediate) { mRequest.addMarker("intermediate-response"); } else { mRequest.finish("done"); } // If we have been provided a post-delivery runnable, run it. if (mRunnable != null) { mRunnable.run(); } } }
可以看出实现了一个Runnable接口,在run方法内部判断是否响应成功,如果成功则调用Request的deliverResponse方法,否则调用deliverError方法。这里的deliverResponse方法内部最终会回调我们在构建Request时设置的Response.Listener
对象,例如StringRequest的deliverResponse内部代码如下。
public class StringRequest extends Request<String> { ...... @Override protected void deliverResponse(String response) { if (mListener != null) { mListener.onResponse(response); } } }
其实performRequest内部转换成Response的处理过程,这里借用Volley源码解析 里的一张图,更加从宏观上清晰的说明问题了:
从上到下表示从得到数据后一步步的处理,箭头旁的注释表示该步处理后的实体类。
关于Cache类
- 缓存接口,代表一个可以获取请求结果、存储请求结果的缓存
- 默认的两个实现子类:NoCache和DiskBasedCache
- DiskBasedCache类会把从服务器返回的信息写入磁盘,然后从磁盘取出缓存,这其中涉及了一些静态的方法如writeInt、writeLong等等,何解?原因之一是Java的IO本身是对byte进行操作,一个int占4个byte,需要按位写入,另一方面也是因为网络字节序是大端字节序,在80x86的平台中,是以小端法存放的,比如我们经过网络发送0x12345678这个整型,但实际上在流中是0x87654321
好了,到这里Volley的整体流程大概梳理了一遍,可能稍微讲得有点乱,当然也仅仅是个人的记录为主,最后,放上Volley官方的请求流程图镇楼(原本想着自己画一张流程图,但翻了翻发觉画不出比这个更好的了):
参考资料
- android-volley
- Volley源码解析
- Android Volley完全解析(四),带你从源码的角度理解Volley
- 重温Volley源码(一):工作流程
- 6、volley 源码解析之工作流程综述
- 7、volley 源码解析之缓存线程工作流程
- 8、volley 源码解析之网络线程工作流程
- 重温Volley源码(二):重试策略
- Volley源码流程分析
- Volley源码流程解析
- Volley源码解析(一)
- Volley源码分析一
- Volley源码解析(一)
- Volley源码分析(一)
- Volley源码解析(一)
- Volley源码及流程分析
- 重温Volley源码(三):添加Cookie或Https的能力
- 9、volley 源码解析之消息分发工的工作流程
- Volley学习(一)Android Volley源码解析
- Volley源码阅读详解(一)---网络任务分发,处理和交付的核心流程
- Volley源码理解之 一
- 1031. Hello World for U (20)
- Hadoop平台作业参数设置关于mapreduce.job.split.metainfo.maxsize的说明
- 好看的动态组织架构图的实现(JavaScript InfoVis Toolkit)
- jsp 语法
- Java线程的三种创建方式
- 重温Volley源码(一):工作流程
- Android网络判断
- 深入浅出nodejs学习笔记--第十、十一章 测试 产品化
- 一个超轻量级的时间处理工具库
- C++模板-Traits
- 4.20信息技术 计算(switch语句)
- 傻逼的规定
- jsp 魔法变量(一些自动计算的变量):注意两端都是下双横线
- android应用因为加入js而导致webview一直在加载中的bug解决方案