3.6 CacheDispatcher & Cache
从volley的工作流程图中,我们已经知道volley的请求工作流程可以分为两个部分,一个是先通过缓存查找请求的数据,如果没有查找成功再通过网络请求。而上面一节已经介绍NetworkDispatcher,已经知道NetworkDispatcher就是负责volley中网络请求的工作,而这节介绍的CacheDispatcher从名字就可以想象得到工作和NetworkDispatcher是类似的,而它正是负责volley的缓存工作,下面就来介绍CacheDispatcher的工作流程。
3.6.1 CacheDispatcher
CacheDispatcher的工作流程跟NetworkDispatcher是类似的,因此参考NetworkDispatcher的工作流程可以将CacheDispatcher的工作流程分为以下几步:
- 通过存储Request的CacheQueue(继承BlockingQueue)中取出Request
- 通过Cacha对象获取Request相应的缓存数据Cache.Entry
- 如果Entry为空,则说明该Request的响应数据没有缓存在Cache当中,则将Request添加到NetworkQueue当中被NetworkDispatcher处理
- 如果Entry过期了,同样将Request添加到NetworkQueue当中
- 如果Entry可使用,则通过Entry构建NetworkResponse,然后通过Request.parseNetworkResponse将NetworkResponse转换成Response对象(这里跟NetworkDispatcher一样)
- 通过ResponseDelivery将Response送回到主线程处理(还是跟NetworkDispatcher一样)
可以看到,CacheDispatcher最后两步和NetworkDispatcher是一样的,只不过前面获取到NetworkResponse的方式不一样,NetworkDispatcher是通过Network执行Request请求从网络上获取到NetworkResponse,而CacheDispatcher是通过Cache从缓存中提取Request对应的NetworkResponse,下面通过流程图来看一下。
从流程图中可以看到形式和NetworkDispatcher几乎是一样的,因此接下来看源码也是十分简单的。
public class CacheDispatcher extends Thread {
private static final boolean DEBUG = VolleyLog.DEBUG;
/** The queue of requests coming in for triage. */
private final BlockingQueue<Request> mCacheQueue;
/** The queue of requests going out to the network. */
private final BlockingQueue<Request> mNetworkQueue;
/** The cache to read from. */
private final Cache mCache;
/** For posting responses. */
private final ResponseDelivery mDelivery;
/** Used for telling us to die. */
private volatile boolean mQuit = false;
/**
* Creates a new cache triage dispatcher thread. You must call {@link #start()}
* in order to begin processing.
*
* @param cacheQueue Queue of incoming requests for triage
* @param networkQueue Queue to post requests that require network to
* @param cache Cache interface to use for resolution
* @param delivery Delivery interface to use for posting responses
*/
public CacheDispatcher(
BlockingQueue<Request> cacheQueue, BlockingQueue<Request> networkQueue,
Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}
/**
* Forces this dispatcher to quit immediately. If any requests are still in
* the queue, they are not guaranteed to be processed.
*/
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();
while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.
final Request request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
}
}
}
跟NetworkDispatcher一样,CacheDispatcher源码中所需要的重点主要有2点:
- 类成员及构造方法,这些成员是CacheDispatcher工作不可缺少的,也不难,只要了解工作流程便可记住了,首先要从队列CacheQueue中取出Request对象,然后通过Cache获取相应的数据,若为空或过期则将Request添加到NetworkQueue中通过网络获取,若不为空则通过ResponseDelivery将数据传送到主线程
- CacheDispatcher也是一个Thread,工作都是在run中进行的,结合上面的流程,可以提取出关键的代码,如下所示
//1.先取出Request对象
final Request request = mCacheQueue.take();
//2.Cache获取request相应的缓存数据
Cache.Entry entry = mCache.get(request.getCacheKey());
//3.如果数据为空或者过期则将request添加到NetworkQueue中
if (entry == null) {
mNetworkQueue.put(request);
continue;
}
if (entry.isExpired()) {
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
//4.Request调用接口方法将NetworkResponse解析成Response对象
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
//5.ResponseDelivery将response回传到主线程处理
mDelivery.postResponse(request, response);
3.6.2 Cache
我们从上面的分析中可以得知,在volley中的请求流程主要是分为两个流程,一个是从缓存里提取数据的缓存流程,由CacheDispatcher实现,另一个是从网络中获取数据,由NetworkDispatcher实现。在CacheDispatcher的工作流程中,提供缓存功能的便是Cache对象,因此我们这节来学习volley中的Cache接口以及它的实现类,学习流程可以对照着NetworkDispatcher中的Network类的学习。
Cache只是一个接口,源码很简单,主要是注意Cache里面的Entry类,该类是主要的缓存信息存储类。
/**
* An interface for a cache keyed by a String with a byte array as data.
*/
public interface Cache {
public Entry get(String key);
public void put(String key, Entry entry);
public void initialize();
public void invalidate(String key, boolean fullExpire);
public void remove(String key);
public void clear();
/**
* Data and metadata for an entry returned by the cache.
*/
public static class Entry {
/** The data returned from cache. */
public byte[] data;
/** ETag for cache coherency. */
public String etag;
/** Date of this response as reported by the server. */
public long serverDate;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
/** Immutable response headers as received from server; must be non-null. */
public Map<String, String> responseHeaders = Collections.emptyMap();
/** True if the entry is expired. */
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/** True if a refresh is needed from the original data source. */
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
}
从代码中可以发现Cache就是提供了简单的增删查基本的数据操作方法,没什么难理解的,接下来看看volley中Cache的实现类DiskBasedCache。
在看DiskBasedCache源码之前,先来回忆一下缓存一般都怎么实现的。我们知道缓存一般分为两种,一种是内存缓存,一种是磁盘缓存,而内存缓存一般使用LruCache实现,磁盘缓存使用DiskLruCache实现。在LruCache中底层则是用accessOrder为true的LinkedHashMap实现的,DiskLruCache是利用文件的读写来实现缓存的实现。
因此可以猜想,DiskBasedCache无非也是用这两种方法进行缓存的,下面具体看一下关键的代码。
DiskBasedCache
/**
* Cache implementation that caches files directly onto the hard disk in the specified
* directory. The default disk usage size is 5MB, but is configurable.
*/
public class DiskBasedCache implements Cache {
/** Map of the Key, CacheHeader pairs */
private final Map<String, CacheHeader> mEntries =
new LinkedHashMap<String, CacheHeader>(16, .75f, true);
/** Total amount of space currently used by the cache in bytes. */
private long mTotalSize = 0;
/** The root directory to use for the cache. */
private final File mRootDirectory;
/** The maximum size of the cache in bytes. */
private final int mMaxCacheSizeInBytes;
/** Default maximum disk usage in bytes. */
private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
/** High water mark percentage for the cache */
private static final float HYSTERESIS_FACTOR = 0.9f;
/** Magic number for current version of cache file format. */
private static final int CACHE_MAGIC = 0x20120504;
/**
* Constructs an instance of the DiskBasedCache at the specified directory.
* @param rootDirectory The root directory of the cache.
* @param maxCacheSizeInBytes The maximum size of the cache in bytes.
*/
public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
mRootDirectory = rootDirectory;
mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
/**
* Constructs an instance of the DiskBasedCache at the specified directory using
* the default maximum cache size of 5MB.
* @param rootDirectory The root directory of the cache.
*/
public DiskBasedCache(File rootDirectory) {
this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
}
@Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
// if the entry does not exist, return.
if (entry == null) {
return null;
}
File file = getFileForKey(key);
CountingInputStream cis = null;
try {
cis = new CountingInputStream(new FileInputStream(file));
CacheHeader.readHeader(cis); // eat header
byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
return entry.toCacheEntry(data);
} catch (IOException e) {
VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
remove(key);
return null;
} finally {
if (cis != null) {
try {
cis.close();
} catch (IOException ioe) {
return null;
}
}
}
}
@Override
public synchronized void put(String key, Entry entry) {
pruneIfNeeded(entry.data.length);
File file = getFileForKey(key);
try {
FileOutputStream fos = new FileOutputStream(file);
CacheHeader e = new CacheHeader(key, entry);
e.writeHeader(fos);
fos.write(entry.data);
fos.close();
putEntry(key, e);
return;
} catch (IOException e) {
}
boolean deleted = file.delete();
if (!deleted) {
VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
}
}
@Override
public synchronized void remove(String key) {
boolean deleted = getFileForKey(key).delete();
removeEntry(key);
if (!deleted) {
VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
key, getFilenameForKey(key));
}
}
public File getFileForKey(String key) {
return new File(mRootDirectory, getFilenameForKey(key));
}
/**
* 判断缓存是否超过阈值,如果超过则删除缓存中的数据
*/
private void pruneIfNeeded(int neededSpace) {
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
return;
}
if (VolleyLog.DEBUG) {
VolleyLog.v("Pruning old cache entries.");
}
long before = mTotalSize;
int prunedFiles = 0;
long startTime = SystemClock.elapsedRealtime();
Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, CacheHeader> entry = iterator.next();
CacheHeader e = entry.getValue();
boolean deleted = getFileForKey(e.key).delete();
if (deleted) {
mTotalSize -= e.size;
} else {
VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
e.key, getFilenameForKey(e.key));
}
iterator.remove();
prunedFiles++;
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
break;
}
}
if (VolleyLog.DEBUG) {
VolleyLog.v("pruned %d files, %d bytes, %d ms",
prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
}
}
private void putEntry(String key, CacheHeader entry) {
if (!mEntries.containsKey(key)) {
mTotalSize += entry.size;
} else {
CacheHeader oldEntry = mEntries.get(key);
mTotalSize += (entry.size - oldEntry.size);
}
mEntries.put(key, entry);
}
/**
* Removes the entry identified by 'key' from the cache.
*/
private void removeEntry(String key) {
CacheHeader entry = mEntries.get(key);
if (entry != null) {
mTotalSize -= entry.size;
mEntries.remove(key);
}
}
……
}
从源码中可以看出,DiskBasedCache并不是单纯的使用LruCache和DiskLruCache进行缓存,而是使用了两者的结合,利用LinkedHashMap来存储头部关键信息,相当于索引,实际存储数据是利用的文件进行操作的。下面通过其中的关键方法来进行解释。
- put:拿put方法来说明,首先调用pruneIfNeeded来保证磁盘中的缓存没有超出阈值,然后根据key获取到相应的文件,将entry.data输出到文件当中,并且通过putEntry(key, e)方法将该key对应的关键信息存到LinkedHashMap中即内存缓存中。这里的LinkedHashMap虽然没有存储实际数据,但它相当于建了一个索引,在通过get获取某缓存数据的时候,判断LinkedHashMap有没有该key的信息,如果有则说明磁盘中有该key对应的数据,此时再去磁盘中获取实际数据。因为LinkedHashMap是存在内存中的,因此先查找LinkedHashMap比在磁盘中查找效率高很多。
- get:上面put方法已经介绍了LinkedHashMap的作用了,就是先通过mEntries.get(key)判断内存中有没有该key对应的数据的关键信息,如果有则说明磁盘中存在该数据,此时再去磁盘中查找,提高了效率。
- pruneIfNeeded:这个方法是在put方法中调用的,目的是保证磁盘缓存不超过阈值,从该方法的源码中可以得知,删除缓存的依据是根据LinkedHashMap的iterator来删除对应数据的,这里充分利用到了LinkedHashMap的LRU算法,因为accessOrder为true的LinkedHashMap会将使用频率少的键值对留在队头,因此最先删除的就是最近最少使用的。
因此,DiskBasedCache的实现原理关键就在于利用了一个LinkedHashMap在内存当中当磁盘缓存文件的索引,实际缓存是通过磁盘来缓存的,这样避免了每次查找都去进行磁盘操作大大提高了效率。
3.7 RequestQueue
从volley的工作流程中可以看出,NetworkDispatcher和CacheDispatcher的分析其实就是volley的全部工作了,但是前面一直没有介绍它们两个是什么时候被调用工作的,这就是本节的内容RequestQueue,回顾一下volley的使用方法,就会发现Request同时也是volley框架工作的整个入口,先看一下volley的使用方法。
volley的使用方法
//1. 创建RequestQueue对象
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
//2. 创建Request对象并在Listener中处理回调对象,在回调方法中更新UI
StringRequest stringRequest = new StringRequest(
"http://10.132.25.104/JsonData/data1.php",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// TODO Auto-generated method stub
textView.setText(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG,error.getMessage());
}
});
//3. 将Request添加至RequestQueue
queue.add(stringRequest);
可以发现,RequestQueue中最主要就是构造方法和add方法,现在就来看一下RequestQueue的源码是怎么实现的,还是只挑选重要的部分。
public class RequestQueue {
private AtomicInteger mSequenceGenerator = new AtomicInteger();
private final Map<String, Queue<Request>> mWaitingRequests =
new HashMap<String, Queue<Request>>();
private final Set<Request> mCurrentRequests = new HashSet<Request>();
private final PriorityBlockingQueue<Request> mCacheQueue =
new PriorityBlockingQueue<Request>();
private final PriorityBlockingQueue<Request> mNetworkQueue =
new PriorityBlockingQueue<Request>();
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
private final Cache mCache;
private final Network mNetwork;
private final ResponseDelivery mDelivery;
private NetworkDispatcher[] mDispatchers;
private CacheDispatcher mCacheDispatcher;
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
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();
}
}
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
public Request add(Request 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;
}
}
……
}
源码中关键的点有3个:
- 类成员以及构造方法,可以看出,RequestQueue里面的成员其实就是NetworkDispatcher和CacheDispatcher两个类以及它们构造方法所需要的成员,比如NetworkQueue,CacheQueue,Network,Cache,ResponseDelivery,注意这里便是传入主线程Handler的地方,可以说volley框架主要的工作配置在这里进行的。
还有看RequestQueue最简单的构造方法,可以发现创建RequestQueu对象最少需要一个Cache和一个Network就可以了,原因也不难理解,volley工作中最主要的类就是两个Dispatcher,而这两个Dispatcher的核心工作类就是Cache和Network。 - start()方法,该方法是RequestQueue的入口方法,在该方法中初始化并启动volley中两个主要工作类CacheDispathcer和多个NetworkDispatcher,此时两个工作类便循环的从各自的队列CacheQueue和NetworkQueue中取出Request进行处理。
- add(Request request)方法,上面说了,两个Dispatcher从各自队列中取出Request执行,那么它们各自的队列的Request是从哪里来的呢?答案就是RequestQueue的add方法,从该方法源码中可以看到,首先先判断添加的Request需不需要被缓存,如果不需要则将Request添加进NetworkQueue当中,否则便将其添加至CacheQueue当中。
注意,在RequestQueue成员中的NetworkDispatcher是一个数组形式,即一个RequestQueue中有多个NetworkDispathcer同时在工作,每个NetworkDispatcher实际上就是一个线程Thread,因此NetworkDispathcer数组其实就是一个线程池!而且该线程池大小为4。所以不要认为没有使用ThreadPoolExecutor就是没有用线程池了。
以上就是RequestQueue的工作流程,是不是很简单,就是启动两个主要工作流程Dispatcher,然后通过add方法将Request添加至相应的Queue当中。
3.8 Volley
上面几节已经将volley框架的整个工作流程都介绍清楚了,最后只剩下整个工作流程最开始的入口点也就是Volley对象,它的作用是在框架工作之前配置好工作所需的参数,比如缓存的目录,使用的网络请求的引擎HttpStack类型等等,工作比较简单,我们来直接看Volley类的源码看它的工作是什么。
public class Volley {
/** Default on-disk cache directory. */
private static final String DEFAULT_CACHE_DIR = "volley";
/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
*
* @param context A {@link Context} to use for creating the cache dir.
* @param stack An {@link HttpStack} to use for the network, or null for default.
* @return A started {@link RequestQueue} instance.
*/
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
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 = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
}
可以看到,Volley中只有newRequestQueue这么一个方法,在这个方法中,最主要的工作就是构建RequestQueue并启动它,而在RequestQueue那节可以知道,构建RequestQueue最主要就是构建Cache和Network对象,而构建Cache主要就是配置缓存目录,构建Network就是选择HttpStack请求引擎。综上所述,Volley的工作就是配置缓存目录和HttpStack以构建RequestQueue并启动它。
4 volley总结
最后在介绍完volley框架中主要的工作类之后,再来看看volley的工作流程便很清晰了。
可以看出来volley的流程主要分为3步,这里结合上面实际的类仔细分析volley的工作流程:
- 通过Volley.newRequestQueue()创建RequestQueue对象,然后再RequestQueue.start()中配置并启动一个CacheDispatcher也就是负责缓存工作的Cache Thread,同时启动4个NetworkDispatcher用于负责网络工作的Network Threads,也就是线程池。
然后通过RequestQueuet通过add(Request r)添加Request请求到队列中,首先是判断该Reqeust是否应该被缓存,如果不需要则将Request添加到NetworkQueue当中给NetworkDispatcher执行,否则将Request添加进CacheQueue通过CacheDispatcher进行处理。 - CacheDispatcher的执行流程为:从CacheQueue当中不断取出Request进行处理,取出一个Reuqest之后先通过Cache获取对应的响应数据,若数据为空或者过期,则将Request添加进NetworkQueue当中;如果数据可用,则通过request.parseNetworkResponse将数据封装成Response类对象,最后通过ResponseDelivery.postResponse将Response传回主线程处理。
- 由上面可以看到,当CacheDispatcher处理不了Request时便会将Request转移到NetworkQueue中让NetworkDispatcher处理。NetworkDispatcher的处理流程为:先从NetworkQueue中取出Request(就是在CacheDispatcher中放进来的),然后通过Network执行该请求获取到NetworkResponse,然后通过request.parseNetworkResponse转化为Response,然后根据需要将Response中的数据缓存到Cache当中(也就是CacheDispatcher中的那个Cache),最后也是通过ResponseDelivery将Response传回到主线程处理。
以上就是volley的实现原理。最后提一下volley中一些小细节的地方吧。
- 线程:缓存线程有1个,网络线程有4个
- 缓存大小:5M
- 使用情景:十分频繁的小数据的网络通信
与Okhttp的关系:volley是一个封装了处理请求、加载、缓存、线程和回调等过程,而Okhttp可以用来提供传输层的功能,也就是处理加载的功能,而且Okhttp的功能更强大,因此可以使用Okhttp代替volley中的传输层功能(如用OKHttpStack代替HurlStack),这样可以使得volley加载功能更强大,所以这就是volley+Okhttp
注意事项:
- Android 6.0之后就将apache的包从SDK中移除了,而volley中又实用了apache的类,因此在6.0之后使用volley时,需要引入手动添加apache的包
- 在使用第三方的网络工具包比如volley或者okhttp时,不管使用的是什么作为网络请求的处理,都应该自己再进行封装一层,而不是直接调用第三方包里面的方法,这样免得在修改第三方网络库的时候大改特改;如果自己封装了一层,则主程序中几乎不用做改动,而只需在自己封装那一个类里面进行替换