上一篇说了Volley的请求流程,可是没有说请求到response后怎么处理,这篇文章就来详细的说一说。让咱们回忆一下,不论是在CacheDispatcher仍是NetworkDispatcher中只要得到response,就经过这种方式传递出去java
mDelivery.postResponse(request, response);复制代码
让咱们探进去看看都发生了什么?算法
@Override
public void postResponse(Request<?> request, Response<?> response) {
postResponse(request, response, null);
}
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}复制代码
前一个函数是直接调用第二个函数,方法中前两句都是作标记,重点看最后一行代码,首先mResponsePoster是个啥东西呀,缓存
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};复制代码
Executor仅仅是一个接口,它只有一个execute方法,你们也看见了,须要一个参数Runnable。也就是说他其实就是一个包装,而后放进handler执行,那么这个handler是哪一个handler?bash
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}复制代码
他实际上是在RequestQueue的构造函数里面进行初始化的。想一想也是,得到数据之后通常都要进行UI操做,因此必须得放在主线程中操做。好了,相比你们的好奇心都没了吧,让咱们来看主线。刚才说到他须要一个Runnable,里面传的是ResponseDeliveryRunnable,让咱们跟进去在看,网络
public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
mRequest = request;
mResponse = response;
mRunnable = runnable;
}
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();
}
}
}复制代码
直接看run方法,前面都是对request进行判断,若是取消的话就直接结束,而后无论成功或者失败,都经过request来发送这个response,探进去看看request这个方法:框架
@Override
protected void deliverResponse(String response) {
if (mListener != null) {
mListener.onResponse(response);
}
}复制代码
public StringRequest(int method, String url, Listener<String> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}复制代码
两个结合能够看出直接把response传递给Listener listener,它就是Respnose中定义的接口,是否是有点熟悉,其实就是咱们初始化request定义的两个监听器中的其中一个,另外一个同理,就不贴出来了。原来最后将response的成功或者失败都交给咱们处理,联系上边的知道咱们的处理方法都被放在了主线程的handler,因此能够放心进行UI操做。这下流程大致都清楚了吧。细心的同窗会发现ResponseDeliveryRunnable中还能够传递一个runnable,这个是怎么用呢,用在哪呢,其实这儿Volley只有一个地方用到了,就是在CacheDispatcher中,假若有些response的soft-TTL(response存活时间)到了,就会发送一个runnable,让他从新进行网络请求获取response,假如返回的是304(就是不须要更新),就仅仅更新一下他的存活时间,什么也不作。假如返回的是一个新的response,就会在NetworkDispatcher中从新发送给request进行再一次操做。把代码贴出来让大家再回顾一下。ide
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);
.............
final Request<?> finalRequest = request;
mDelivery.postResponse(request, response,
new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(finalRequest);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}复制代码
中间有一些省略,看重点就能够了。
上篇文章说这一篇讲一下缓存的精彩之处,可是想一想仍是要把Volley的流程所有要搞明白,因此就。。。下面说说缓存是怎么精彩的,先说一部分,也是最精彩的部分,至少是我认为的。
你们一说到缓存,就能想到二级缓存,三级缓存(其实也就是二级),lru算法等。那么Volley中有没有呢?网上有人说没有lru,这儿我是不赞同的。来看看我为何不赞同,同时但愿大家有本身的判断。
直接看缓存的类,他有一个接口Cache,让咱们看他的子类,函数
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;
/** 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;
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);
}
/**
* Returns the cache entry with the specified key if it exists, null otherwise.
*/
@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 BufferedInputStream(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;
} catch (NegativeArraySizeException 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;
}
}
}
}
/**
* Puts the entry with the specified key into the cache.
*/
@Override
public synchronized void put(String key, Entry entry) {
pruneIfNeeded(entry.data.length);
File file = getFileForKey(key);
try {
BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
CacheHeader e = new CacheHeader(key, entry);
boolean success = e.writeHeader(fos);
if (!success) {
fos.close();
VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
throw new IOException();
}
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());
}
}复制代码
这儿我只放了一些重点的代码,看缓存固然是要看他的get和put方法。我先说一个java的集合--LinkedHashMap,它保证了插入的顺序和读取的顺序是一致的,还内置了LRU算法,这是关键。好了,来看代码:他首先有一个LinkedHashMap的成员变量mEntries,以request的url为key,CacheHeader为value存放在该变量中。而CacheHeader是一个轻量级的类,里面的成员变量和方法并很少。看名字就知道,该类仅仅是存放response的head,里面只是response的一些说明信息,并无真正的数据。还有一个mRootDirectory,这里面才是存放真正的数据,默认大小为5M。oop
先看get方法,先从mEntries获取一个CacheHeader,若是为空就直接返回,不为空就从文件中取出相应的数据,最后转化成CacheEntry返回。完了,再来看put方法,首先判断空间是否装下传过来的Entry,先假设能装的下,而后就直接写入磁盘,也就是file中。同时也写入map中,就是这个方法putEntry(key, e);而后再说它是怎么判断的,直接看代码吧post
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);
}
}复制代码
首先看当前的大小和须要的容量的和是否比最大容量小,小的话就直接返回,假如不够的话,从mEntries中获取他的迭代器,而后不断获取CacheHeader ,而后再从CacheHeader 取得key,再从file中删除对应的缓存,而后也从mEntries删除。而后再看容量是否知足所须要的。不知足再不断的循环,直到知足为止。这儿有一个关键,首先它利用LinkedHashMap的内置LRU算法,而后仅仅是将缓存头部信息添加到内存,也就是Map中,而后将数据放在磁盘里。当添加或者删除的时候,都会先从Map中查询,这样大大减小磁盘操做,同时磁盘是有容量的,当添加时候容量不够了,会先从Map中删除,同时将磁盘中也删除,这样它两就是联动啊,同时拥有了LRU算法和容量,真特么精彩。好了,这篇文章也就完了。具体Volley有没有实现lru,你们自行判断。Volley的流程也说完了,接下来的文章会探讨它的一些代码技巧、框架结构、打log 的方式等。要是文章有什么错误或者不稳妥的地方,还望你们指出来,一块儿讨论提升。欢迎阅读!