上篇文章我们走了一遍Volley的大致流程,本章着重对请求的多级取消和缓存作讲解。
请求的取消
/**
* Cancels all requests in this queue with the given tag. Tag must be non-null
* and equality is by identity.
*/
public void cancelAll(final Object tag) {
if (tag == null) {
throw new IllegalArgumentException("Cannot cancelAll with a null tag");
}
cancelAll(new RequestFilter() {
@Override
public boolean apply(Request<?> request) {
return request.getTag() == tag;
}
});
}
通过标记取消指定的请求
/**
* Cancels all requests in this queue for which the given filter applies.
* @param filter The filtering function to use
*/
public void cancelAll(RequestFilter filter) {
synchronized (mCurrentRequests) {
for (Request<?> request : mCurrentRequests) {
if (filter.apply(request)) {
request.cancel();
}
}
}
}
这两段可以看出,通过遍历mCurrentRequests,取出标识为tag的request,然后将此调用此request的取消方法。
/**
* Mark this request as canceled.
*
* <p>No callback will be delivered as long as either:
* <ul>
* <li>This method is called on the same thread as the {@link ResponseDelivery} is running
* on. By default, this is the main thread.
* <li>The request subclass being used overrides cancel() and ensures that it does not
* invoke the listener in {@link #deliverResponse} after cancel() has been called in a
* thread-safe manner.
* </ul>
*
* <p>There are no guarantees if both of these conditions aren't met.
*/
// @CallSuper
public void cancel() {
synchronized (mLock) {
mCanceled = true;
mErrorListener = null;
}
}
/**
* Returns true if this request has been canceled.
*/
public boolean isCanceled() {
synchronized (mLock) {
return mCanceled;
}
}
然后设置request的变量mCanceled=true,外界通过isCanceled来获取request的状态。
共有四个处方调用了isCanceled:CacheDispatcher、RequestFuture、ExecutorDelivery、NetworkDispatcher,抛开RequestFuture不讲。
CacheDispatcher和NetworkDispathcer是在请求前进行判断的,ExecutorDelivery是在请求成功后进行判断的。
CacheDispathcher和NetworkDispathcer中,当从缓存队列或网络请求队列中取出一个请求,如果这个请求被取消则不进行网络请求,以CacheDispathcher为例:
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");
return;
}
ExecutorDelivery中,当请求成功后,判断当前请求时是否被取消,如果已经被取消,则网络请求返回的数据不返回到主线程:
// 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);
}
我们看到这三个地方都调用了request的finish方法:
/**
* Notifies the request queue that this request has finished (successfully or with error).
*
* <p>Also dumps all events from this request's event log; for debugging.</p>
*/
void finish(final String tag) {
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
if (MarkerLog.ENABLED) {
final long threadId = Thread.currentThread().getId();
if (Looper.myLooper() != Looper.getMainLooper()) {
// If we finish marking off of the main thread, we need to
// actually do it on the main thread to ensure correct ordering.
Handler mainThread = new Handler(Looper.getMainLooper());
mainThread.post(new Runnable() {
@Override
public void run() {
mEventLog.add(tag, threadId);
mEventLog.finish(Request.this.toString());
}
});
return;
}
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
}
}
每个请求都持有RequsetQueue对象,然后调用其finish方法:
/**
* Called from {@link Request#finish(String)}, indicating that processing of the given request
* has finished.
*/
<T> void finish(Request<T> request) {
// Remove from the set of requests currently being processed.
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
}
将requestQueue中的请求集合删掉此请求,至此请求的取消完全结束。
缓存
数据缓存
在实例化requestQueue的时候 volley创建了一个缓存对象:
private static RequestQueue newRequestQueue(Context context, Network network) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
我们可以去看下DisBasedCache的内部结构:
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 = 0x20150306;
//VisibleForTesting
static class CacheHeader {
/** The size of the data identified by this CacheHeader. (This is not
* serialized to disk. */
long size;
/** The key that identifies the cache entry. */
final String key;
/** ETag for cache coherence. */
缓存标签
final String etag;
/** Date of this response as reported by the server. */
服务端返回数据的主要内容,当然也是要存储的内容
final long serverDate;
/** The last modified date for the requested object. */
服务器返回数据的时间
final long lastModified;
/** TTL for this record. */
缓存过期时间
final long ttl;
/** Soft TTL for this record. */
final long softTtl;
/** Headers from the response resulting in this cache entry. *
服务端返回的头部信息
final List<Header> allResponseHeaders;
}
除此之外 还有基类Cache中的一个内部类:
/**
* Data and metadata for an entry returned by the cache.
*/
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;
/** The last modified date for the requested object. */
public long lastModified;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
/**
* Response headers as received from server; must be non-null. Should not be mutated
* directly.
*
* <p>Note that if the server returns two headers with the same (case-insensitive) name,
* this map will only contain the one of them. {@link #allResponseHeaders} may contain all
* headers if the {@link Cache} implementation supports it.
*/
public Map<String, String> responseHeaders = Collections.emptyMap();
/**
* All response headers. May be null depending on the {@link Cache} implementation. Should
* not be mutated directly.
*/
public List<Header> allResponseHeaders;
/** 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();
}
}
DiskBasedCache中的内部类CacheHeader中的部分参数与基类Cache中的内部类Entity的参数是完全一样,具体原因下一篇文章会有提到,这里简单说一下,
private CacheHeader(String key, String etag, long serverDate, long lastModified, long ttl,
long softTtl, List<Header> allResponseHeaders) {
this.key = key;
this.etag = ("".equals(etag)) ? null : etag;
this.serverDate = serverDate;
this.lastModified = lastModified;
this.ttl = ttl;
this.softTtl = softTtl;
this.allResponseHeaders = allResponseHeaders;
}
/**
* Instantiates a new CacheHeader object.
* @param key The key that identifies the cache entry
* @param entry The cache entry.
*/
CacheHeader(String key, Entry entry) {
this(key, entry.etag, entry.serverDate, entry.lastModified, entry.ttl, entry.softTtl,
getAllResponseHeaders(entry));
size = entry.data.length;
}
再cacheHeader的构造器中,通过传进来一个Entity类型的参数,然后将Entity中的参数全部都赋值到CacheHeader中的参数内。
在CacheHeader中有put和putEntity两个方法,顾名思义,都是传入Entity的方法,但是public类型的方法是put,所以put是从外部调用的最初方法:
/**
* 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(createOutputStream(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());
}
}
可以看到,进入put首先判断将eneity.data的字节长度做为参数传入了pruneIfNeeded方法,我们简单看下:
/**
* Prunes the cache to fit the amount of bytes specified.
* @param neededSpace The amount of bytes we are trying to fit into the cache.
*/
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;
}
}
}
这个方法主要是判断缓存空间是否足够放下本次的entity.data,如果能放下则直接返回,如果不能放下,则需要删除一些缓存文件,直到在放下entity.data之后还剩余至少10%的空间量,其中mTotalSize为缓存的总字节长度。
回到put方法我们继续看,首先根据key值也就是url创建一个file对象,然后以流的方式将cacheHeader中的标识参数写入到file中,完成之后再讲entity.data写入到文件中,最后通过putEntity计算刷新缓存文件的总字节长度。
除了put之外,还有get、remove、clear三个方法,内容不再多说。
网友评论