简介
volley框架是Google公司开发的一套网络访问开源框架,具有体积小、效率高和适合频繁的网络请求等特点;虽然现在的网络主流都推荐用Retrofit+Okhttp,但是lz认为用什么样的网络框架还是得根据什么样的项目,不是一种功夫就可以打遍天下无敌手的;所有这里分析Volley的目的不一定是去用,而是学习分析Volley的代码设计、网络请求、缓存设计等知识,补充我们的知识,更好的帮助我们开发今后的工作。
知识点简介
本文将以以下几个方面逐点分析
- Volley由哪些重要的成员组成 ?
- Volley执行的流程如何以及它的成员运行流程如何 ?
- Volley一些特性点怎么实现,缓存设计、HTTPS等
Volley重要成员
NetWork.java
网络控制中心,执行真正的网络请求部分,所有的请求最终执行网络访问就是它来完成的
Request.java
Volley提供的网络请求类,封装网络请求参数
RequestQueue.java
这个类很重要,网络请求队列类,所有的Request都是要add到这个类中,而网络线程和缓存线程都是从这个队列里面去取
CacheDispatcher.java
缓存线程,默认只有一个,这个线程用于处理一些具有缓存特性的请求任务,如果有缓存就去缓存中取,没有或者该任务是实时性的,就需要重新把该任务添加到网络线程中去执行
NetworkDispatcher.java
网络线程,所有的网络请求都是由这类型网络线程发出执行的,Volley默认该线程由4个,协同处理所有的请求Request任务
数据结构:
Set<Request<?>> mCurrentRequests: 存储所有的请求Request
PriorityBlockingQueue<Request<?>> mCacheQueue: 缓存队列,存储所有有缓存特性的请求
PriorityBlockingQueue<Request<?>> mNetworkQueue:网络队列,存储需要进行网络访问的请求
Map<String, Queue<Request<?>>> mWaitingRequests:待会再解释,不清楚
Volley源码分享 – 从最简单的使用方法入手
RequestQueue queue = Volley.newRequestQueue(this);
StringRequest request = new StringRequest(Request.Method.GET, "https://www.baidu.com/", new Response.Listener<String>() {
@Override
public void onResponse(String response) {
tv.setText(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
tv.setText("error"+error.getMessage());
}
});
queue.add(request);
Volley.newRequestQueue(this)
追踪进去发现它调用了另一个方法newRequestQueue(Context context, HttpStack stack)
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
......
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack(); //内部使用HttpURLConnection
} else {
//内部使用HttpClient,但是现在已经无法使用了
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
以上任务有三:
- 创建缓存目录;
- 实例化网络控制核心NetWork,api>9使用HurlStack,<9的使用HttpClient,追踪AndroidHttpClient.newInstance,发现它直接抛了一个异常,说明现在<9的是无法使用Volley
- 实例化RequestQueue,并传递了缓存类和网络控制类,RequestQueue很重要,继续分析:
RequestQueue构造方法
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
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;
}
上面完成了RequestQueue的成员赋值,实例化了DEFAULT_NETWORK_THREAD_POOL_SIZE(4)个网络线程,还加上一个回调到主线程的工具类ExecutorDelivery;ExecutorDelivery就是内部封装了handler.postRunnable方法,具体可以点击查看源码
回到newRequestQueue执行queue.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();
}
}
默认情况下,是创建一个CacheDispatcher缓存线程和4个NetworkDispatcher网络线程并运行,它们两个的大致工作就是轮询去mCacheQueue或者mNetworkQueue队列取出Request请求,并执行;
上面代码还有一个很重要的知识点,当前线程如何停止其他线程?解决办法就是该方法的第一条stop()方法,Java默认是无法直接停止其他线程的,只有利用interrupt产生中断异常,由其他线程设置标志位自行完结自己
回到Volley最简单的用法 — RequestQueue.add(Request)
上面示例的StringRequest不是设计主要的就暂不分析了,如有兴趣的可点击这里
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); //所有的Request都要加入mCurrentRequests队列中去,方便管理
}
// 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<>();
}
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;
}
}
以上完成任务:
- 将所有Request都加入mCurrentRequests这个Set集合里面来,方便管理;比如有时想取消某个Request就会从set.remove(Request)
- 如果Request任务不是一个缓存特性的请求,就直接加入到mNetworkQueue队列去,方法结束
- Request是一个缓存特性,就要根据一定的逻辑加入到mCacheQueue队列里面去;分析这个逻辑:
Map<String, Queue<Request<?>>> mWaitingRequests;它是一个Map集合key为String存储访问的url,value为一个Queue队列存储Request;按照上面的代码实现可以得出,其目的就是防止相同的网络访问url重复访问,先判断Map里面有相同的url的key没?如果有,就把request加到value中去;没有就把value设置成null,为什么没有就设置为null,因为为null的时候这个Request是第一个用这个url访问的,把它加入到缓存队列即可,后续的就不需要添加了;
__ mWaitingRequests除了上面的过滤相同请求功能之后,怎么在网络请求完成后回调的?__
这里大致阐释流程,后续会详细讲解:
- mCacheQueue里面的Request请求完成并且回调后,会调用自身的finish方法
- finish再次回调用RequestQueue的finish方法,在此方法中
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null) {
if (VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
// Process all queued up requests. They won't be considered as in flight, but
// that's not a problem as the cache has been primed by 'request'.
mCacheQueue.addAll(waitingRequests);
}
}
}
它会将相同的key的value,从mWaitingRequests的Map集合中取出来,在从新假如到mCacheQueue中去,在CacheDispatcher线程中在轮训mCacheQueue队列把上次网络访问的数据赋值给新的请求,就不必再从新进行一次网络请求
总结
一张流程图
至此,RequestQueue重要方法已经分析差不多了;以上是lz一点见解,如有不足还请指教;明天再继续分析CacheDispatcher和NetworkDispatcher线程执行流程
HttpClient浅析
阻塞式请求访问,底层设置有最大连接数和单个路由的最大连接数,来一次请求,就会重连接池里面取出一个connection,在此基础上进行网络访问
OkHttpClient
在此基础上多了一个Dispatcher调度,一个同步双端队列和两个异步双端队列,两个异步队列分别存储正在执行网络请求和准备执行的网络请求,默认最大连接数64个,单个路由最大5个,当来了一个请求判断是同步还是异步请求,同步就加入同步队列,异步就判断连接总数和单个路由有没有超,没超就从连接池取出一个连接,在把请求加入正在执行的队列,拦截器处理,完成请求并移除执行队列;后面来的请求再次走这个过程