前言
volley使用已久,一直想阅读下其源码,然后对比现在常用的一些网络框架,然后对网络这块能有更加的理解。
大致的想法是:先了解volley的整体框架,再逐个理清涉及到的各个知识点。然后,再涉及一下OkHttp等,进而再去了解下http的协议细节,从而对网络这块能有个充分的认识。
android上手之时太过仓促,基础这块一直觉得不够扎实。而在我看来,对基础技术的掌握应该是和对业务需求的理解同样重要的。
正文
volley相关的文章虽不至多如牛毛但也不少,大致分为两类:分析原理和侧重应用。
讲应用的可以参考:郭神的几篇博客;原理分析比较好的有这篇:
http://a.codekk.com/detail/Android/grumoon/Volley%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90
其实这篇文章已经将volley的骨架讲得很清楚了。而我想做是,记录我自己阅读理解的这个过程。
概述
volley由两部分组成:核心部分和toolbox中的工具类。
RequestQueue
用过volley的都清楚,咱们的起手式:Volley.newRequestQueue(context)创建一个queue然后调用add()就可以发起网络请求了。而这招就是创建一个RequestQueue,看它位置那么中间一看就很重要。
RequestQueue顾名思义:请求队列,带了一票的成员和方法。
RequestQueue本质:存储请求然后让dispatcher来处理请求。这话有两层意思:1)它存储各类请求;2)它驱使dispatcher。
1,先跳过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();
}
}
start中初始化了CacheDispatcher和NetworkDispatcher。这两类dispatcher就好比两类工种,他们是Request的具体执行者。当然,不同工种的工作量不同。所以,CacheDispatcher只有一个,NetworkDispatcher是一组,NetworkDispatcher的数量是在创建RequestQueue时指定的,当然volley也设了默认值:DEFAULT_NETWORK_THREAD_POOL_SIZE=4。
2,dispatcher们的创建
public CacheDispatcher(
BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
Cache cache, ResponseDelivery delivery)
public NetworkDispatcher(BlockingQueue<Request<?>> queue,
Network network, Cache cache,
ResponseDelivery delivery)
mCacheQueue和mNetworkQueue分别是用于缓存和网络两个优先级阻塞队列。而mCache和mNetwork分别是实现缓存和网络的两把工具,在RequestQueue的创建时被传入,这两把工具分别有两个接口:Cache和Network。volley定义了这两把工具的功能要求,君可按这标准自己做两把称手的家伙,如果木有也没事,volley默认提供两把:DiskBasedCache(基于文件的缓存策略)和BasicNetwork。
(点评:可扩展&面向接口编程的完美体现,这两个默认实现类自然是放在toolbox目录中)。
至于ResponseDelivery的默认实现ExecutorDelivery被放在了核心包中,可能是由于结果分发这块相比于缓存和网络实在没什么花头吧。
3,Request的添加
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;
}
}
add的流程:1)不需要缓存的(Request默认开启缓存)就直接扔进mNetworkQueue;2)需要缓存的,先检测mWaitingRequests(保存了出于等待状态的请求)判断请求是否重复,不重复就记录请求并扔进mCacheQueue。
大致呢就是:每个请求过来先在mCurrentRequests登记姓名。不要缓存的直接扔给网络dispatcher,可以缓存的扔给缓存dispatcher,同一个缓存还来就给我在mWaitingRequests里按Cachekey排排站好。
4,RequestQueue的总结
RequestQueue创建的时候需要三把工具(缓存Cache、网络Network、结果分发ResponseDelivery),然后自己再创建了几个存储队列(mCacheQueue、mNetworkQueue),利用这两样东西创建了CacheDispatcher和networkDispatcher。
接着,视情况分配Request给不同的dispatcher来处理。它提供了Request执行完成后的善后方法(finish),但没负责Request的善后。
技术细节
1,在操作mCurrentRequests和mWaitingRequests添加和删除的每一处都是用了同步机制,利用的是java的对象锁,不得不说synchronized(Object)真方便,比C语言实现同步便利得多。
1-1)mCurrentRequests类型是HashSet,而mWaitingRequests是HashMap。mCurrentRequests仅需要添加和删除不需要索引,为何还要涉及hash。照我的理解如果不需要索引单就保存来说应当是极好实现的,不知用HashSet是否是出于利于存储方面的考虑,作为遗留问题后续阅读源码再加分析。
1-2)Set和Map是不带同步机制的,之前看ArrayList等源码的时候,貌似同步是可以通过Collections.synchronizedList来加持一层同步buff从而实现的。那为何此处并未使用Collections机制,是考虑方便还是其它,留待用到再深究。