开始
-
Volley的基本用法
// 创建请求队列 RequestQueue queue = Volley.newRequestQueue(getApplicationContext()); String url = "http://xxx"; // 获取字符串使用StringResult,获取JsonObject使用JsonObjectRequest,获取Bitmap使用ImageRequest StringRequest stringRequest = new StringRequest(Request.Method.GET,url,new Response.Listener<String>() { @Override public void onResponse(String response) { // 回调在主线程 } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // 回调在主线程 } }); // 设置tag当调用queue.cancelAll(tag)会取消所有拥有这个tag的request,一般用于在onStop()中关闭网络连接 stringRequest.setTag(this); queue.add(stringRequest);
-
本文的流程图如下
-
由上述代码可知,使用Volley的主要步骤包括创建RequestQueue、创建Request、将Request加入到RequestQueue三步,下面一一讲述
源码
1. RequestQueue的创建
-
1.1 常规用法
常规做法是调用Volley.newRequestQueue这个API创建,代码如下// MainActivity.java RequestQueue queue = Volley.newRequestQueue(getApplicationContext()); // Volley.java public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, (BaseHttpStack) null); } public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) { BasicNetwork network; if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { network = new BasicNetwork(new HurlStack()); } else { // 小于2.3使用HttpClient忽略 } } else { network = new BasicNetwork(stack); } return newRequestQueue(context, network); } private static RequestQueue newRequestQueue(Context context, Network network) { // network = BasicNetwork(new HurlStack()) // DEFAULT_CACHE_DIR = "volley" File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); queue.start(); return queue; } public void start() { stop(); // PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<>(); // PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<>(); // 这里的mCacheQueue、mNetWorkQueue都是优先级阻塞队列mCache是传入的DiskBaseCache // mDeliver则是在构造器里面创建的ExecutorDelivery其拥有一个Looper是主线程的Handler mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); mCacheDispatcher.start(); for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } }
-
1.2 PriorityBlockingQueue 优先级阻塞队列
可以把RequestQueue当做缓存队列和网络请求队列的生产者,NetworkDispatcher、CacheDispatcher当做消费者,当然有时候CacheDispatcher也会被当做网络请求队列的生产者(缓存过期、没命中等情况),Request内有一个枚举类定义了4种优先级,默认情况是NORMAL,我们可以重写Request.getPriority从而改变优先级,PriorityBlockingQueue会调用Request.compareTo来判断顺序(优先级越高越先出去,优先级相等则按照FIFO)public enum Priority { LOW, NORMAL, HIGH, IMMEDIATE } @Override public int compareTo(Request<T> other) { Priority left = this.getPriority(); Priority right = other.getPriority(); return left == right ? this.mSequence - other.mSequence : right.ordinal() - left.ordinal(); }
-
1.3 mCacheDispatcher.start
因为CacheDispatcher派生自Thread所以直接看其run方法@Override public void run() { if (DEBUG) VolleyLog.v("start new dispatcher"); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 调用实现类,初始化缓存比如从磁盘读取缓存文件 mCache.initialize(); while (true) { try { processRequest(); } catch (InterruptedException e) { // 如果是主动调用quit出循环的,再调用interrupt退出循环 if (mQuit) { Thread.currentThread().interrupt(); return; } } } }
-
1.3.1 下面首先看看mCache.initialize(),在这里mCache对应于DiskBasedCache
public synchronized void initialize() { // 目录不存在就创建目录,由于目录都没缓存文件肯定也没直接返回 if (!mRootDirectory.exists()) { if (!mRootDirectory.mkdirs()) { VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath()); } return; } // 获得目录下所有的文件 File[] files = mRootDirectory.listFiles(); if (files == null) { return; } for (File file : files) { try { long entrySize = file.length(); // 这个装饰类,只是在原有的BufferedInputStream上加上获取剩余字节的方法 CountingInputStream cis = new CountingInputStream( new BufferedInputStream(createInputStream(file)), entrySize); try { CacheHeader entry = CacheHeader.readHeader(cis); // 通过初始化的entry其size等于缓存文件的大小,当网络请求回来其size等于响应体的大小 entry.size = entrySize; // 保存到mEntries注意这里LinedHashMap是有序的按照LRU的顺序排序 putEntry(entry.key, entry); } finally { cis.close(); } } catch (IOException e) { // readHeader抛出的IOException在这里被处理掉了,删除了文件 file.delete(); } } }
-
1.3.1.1 CountingInputStream
CountingInputStream只是在原有的BufferedInputStream上多维护了一个已经读取多少字节的变量,代码如下static class CountingInputStream extends FilterInputStream { private final long length; private long bytesRead; CountingInputStream(InputStream in, long length) { super(in); this.length = length; } @Override public int read() throws IOException { int result = super.read(); if (result != -1) { bytesRead++; } return result; } @Override public int read(byte[] buffer, int offset, int count) throws IOException { int result = super.read(buffer, offset, count); if (result != -1) { bytesRead += result; } return result; } @VisibleForTesting long bytesRead() { return bytesRead; } // 还有多少字节没有读 long bytesRemaining() { return length - bytesRead; } }
-
1.3.1.2 CacheHeader.readHeader
static CacheHeader readHeader(CountingInputStream is) throws IOException { // 读取4个字节组成一个int,如果该值不等于所谓的魔法数字就抛出异常 int CACHE_MAGIC = 0x20150306 int magic = readInt(is); if (magic != CACHE_MAGIC) { // 这里不用去删除它,最终会被外界捕获到异常将其删除 throw new IOException(); } // 读取这六个属性 String key = readString(is); String etag = readString(is); long serverDate = readLong(is); long lastModified = readLong(is); long ttl = readLong(is); long softTtl = readLong(is); List<Header> allResponseHeaders = readHeaderList(is); // 通过这7个参数构建了CacheHeader对象 return new CacheHeader( key, etag, serverDate, lastModified, ttl, softTtl, allResponseHeaders); }
-
1.3.1.2.1 readInt
// 读取4个字节组成一个int static int readInt(InputStream is) throws IOException { int n = 0; n |= (read(is) << 0); n |= (read(is) << 8); n |= (read(is) << 16); n |= (read(is) << 24); return n; }
-
1.3.1.2.2 readLong
// 读取8个字节组成一个long static long readLong(InputStream is) throws IOException { long n = 0; n |= ((read(is) & 0xFFL) << 0); n |= ((read(is) & 0xFFL) << 8); n |= ((read(is) & 0xFFL) << 16); n |= ((read(is) & 0xFFL) << 24); n |= ((read(is) & 0xFFL) << 32); n |= ((read(is) & 0xFFL) << 40); n |= ((read(is) & 0xFFL) << 48); n |= ((read(is) & 0xFFL) << 56); return n; }
-
1.3.1.2.3 readString
// 首先读取8个字节以用来确定后面那个字符串的长度,然后再读取该长度将其转化为字符串 static String readString(CountingInputStream cis) throws IOException { long n = readLong(cis); byte[] b = streamToBytes(cis, n); return new String(b, "UTF-8"); }
-
1.3.1.2.3.1 streamToBytes
// 从流中读取指定长度的字节,然后返回 static byte[] streamToBytes(CountingInputStream cis, long length) throws IOException { long maxLength = cis.bytesRemaining(); // 长度不能是负数或者大于余下的字节数,并且不能大于int的最大值,这里是根据强转判断的 if (length < 0 || length > maxLength || (int) length != length) { throw new IOException("streamToBytes length=" + length + ", maxLength=" + maxLength); } byte[] bytes = new byte[(int) length]; // 读取length长度的字节 new DataInputStream(cis).readFully(bytes); return bytes; }
-
1.3.1.2.4 readHeaderList
static List<Header> readHeaderList(CountingInputStream cis) throws IOException { // 先读取下有几个响应头 int size = readInt(cis); if (size < 0) { throw new IOException("readHeaderList size=" + size); } List<Header> result = (size == 0) ? Collections.<Header>emptyList() : new ArrayList<Header>(); // 然后一个个读取出来放到result中 for (int i = 0; i < size; i++) { String name = readString(cis).intern(); String value = readString(cis).intern(); result.add(new Header(name, value)); } return result; }
-
1.3.1.3 putEntry DiskBaseCache内部维护这一个HashMap并且是有序的,从里面读取元素是按照LRU的规格读取的,mTotalSize则维护着当然所有缓存文件的总大小,默认是5m,超出后会从mEntries取出删除
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); }
-
1.3.2 processRequest 从缓存阻塞队列中取Request,取不到则阻塞
private void processRequest() throws InterruptedException { // 当Request被添加到缓存队列前会被阻塞 final Request<?> request = mCacheQueue.take(); processRequest(request); }
-
1.4 networkDispatcher.start
public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { try { processRequest(); } catch (InterruptedException e) { if (mQuit) { Thread.currentThread().interrupt(); return; } } } }
-
1.4.1 processRequest 从网络请求阻塞队列中取Request,取不到则阻塞
private void processRequest() throws InterruptedException { // 同样当没有request添加到网络请求队列中也会阻塞 Request<?> request = mQueue.take(); processRequest(request); }
2. Request的创建
- 我们可以自己继承与Request也可以选择直接使用StringRequest、JsonObjectRequest等,下面说说几个重要的方法
-
getCacheKey 该方法会根据Url和Method返回一个字符串,该字符串决定了缓存文件的名称(分成两半取hash值然后相加)
public String getCacheKey() { String url = getUrl(); int method = getMethod(); if (method == Method.GET || method == Method.DEPRECATED_GET_OR_POST) { return url; } return Integer.toString(method) + '-' + url; }
-
getHeaders,Volley会在做网络请求前调用该方法设置请求参数,所以如果我们要自定义请求头那么可以重写该方法,默认是一个空Map
public Map<String, String> getHeaders() throws AuthFailureError { return Collections.emptyMap(); }
-
getParams, 如果该请求方法是Post,Volley会在网络请求前调用该方法将其作为输出源写给HttpUrlConnection.getOutputStream,默认返回null
protected Map<String, String> getParams() throws AuthFailureError { return null; }
-
finish 通知RequestQueue该请求结束了,内部调用RequestQueue.finish,如果日志可以打印的话会打印该请求的所有日志
void finish(final String tag) { if (mRequestQueue != null) { mRequestQueue.finish(this); } // 打印日志续文会讲 } // RequestQueue.finish <T> void finish(Request<T> request) { synchronized (mCurrentRequests) { mCurrentRequests.remove(request); } synchronized (mFinishedListeners) { // 回调监听 for (RequestFinishedListener<T> listener : mFinishedListeners) { listener.onRequestFinished(request); } } }
-
parseNetworkResponse 抽象方法需要各子类实现,解析网络响应(可能来自缓存或者网络请求)包装成合适的Response对象返回,比如StringRequest就包装成Response<String>,如果该方法返回null,那么程序会直接crash
-
cancel 取消请求,如果是在主线程调用的该方法那么就可以保证不会回调Request.deliverResponse和Request.deliverError
-
hasHadResponseDelivered 用于判断当前请求是否已经分发了,主要用于SoftTtl过期了,Ttl没过期,该情况会先返回缓存的响应的响应然后再去请求如果响应码是304就调用该方法判断请求是否已经分发了,如果已经分发了那么就不进行第二次分发了
-
getBodyContentType,决定Post和Put请求body的类型
-
shouldCache,决定该请求是否需要缓存,如果不需要加入到网络请求阻塞队列中(网络请求结束后也不会缓存),否则加入缓存阻塞队列中,我们可以调用setShouldCache来控制是否需要缓存
-
getTimeoutMs、getRetryPolicy都跟重试策略,本文的续文会讲到
-
3. 将Request填加到RequestQueue中
- 3.1 RequestQueue.add 视请求是否需要缓存决定把其放到缓存队列还是网络请求队列中
public <T> Request<T> add(Request<T> request) { // 标志着当前request属于哪个RequestQueue request.setRequestQueue(this); synchronized (mCurrentRequests) { mCurrentRequests.add(request); } // 设置序列号从零开始0、1、2 request.setSequence(getSequenceNumber()); // 设置日志,最后讲 request.addMarker("add-to-queue"); // 如果这个请求不需要缓存,那么跳过加入缓存队列,直接添加到网络请求队列 if (!request.shouldCache()) { mNetworkQueue.add(request); return request; } mCacheQueue.add(request); return request; }
4. 假设Request需要缓存CacheDispatcher就能从缓存队列中取到Request,就能脱离阻塞调用processRequest(Request)
-
4.1 processRequest(Request)
void processRequest(final Request<?> request) throws InterruptedException { request.addMarker("cache-queue-take"); // 如果在被添加到缓存队列前就已经取消了直接结束该请求 if (request.isCanceled()) { // 内部会将自己从RequestQueue中移除,回调RequestFinishedListener.onRequestFinished() // 并且打印日志(如果开启了等级为VERBOSE) request.finish("cache-discard-canceled"); return; } // 尝试从缓存中恢复 Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) { request.addMarker("cache-miss"); // 缓存没命中,如果已经有了相同缓存key的请求并且响应还没下来,那么不需要加入到网络请求队列 // 否则加入到缓存队列中去,这会使4个NetworkDispatcher中的一个脱离阻塞 if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { mNetworkQueue.put(request); } return; } // 如果缓存的内容已经完全过期了那么只能再请求网络,内部判断ttl与System.currentTimeMillis // 满足ttl >= softTtl if (entry.isExpired()) { // 缓存命中了但是过期了 request.addMarker("cache-hit-expired"); // 设置一下过期的缓存内容,内部的etag,last_modify将会在下次请求时带给服务端 request.setCacheEntry(entry); // 与上面一样 if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { mNetworkQueue.put(request); } return; } // 下面就是缓存命中了,并且没有完全过期 request.addMarker("cache-hit"); // 组装NetWorkResponse对象,给Request解析 Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); // 判断softTtl < System.currentTimeMillis() if (!entry.refreshNeeded()) { // 如果softTtl也小于当前时间,那么缓存有效,直接分发响应 mDelivery.postResponse(request, response); } else { // softTtl已经大于当前时间了,也可以直接分发这个响应,但是分发后还要再去请求一下网络 request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); // 标记当前的响应是中间态,可能该请求会触发两次响应 response.intermediate = true; if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { // 分发响应然后再把请求放入网络请求队列中 mDelivery.postResponse( request, response, new Runnable() { @Override public void run() { try { mNetworkQueue.put(request); } catch (InterruptedException e) { // Restore the interrupted status Thread.currentThread().interrupt(); } } }); } else { // 已经有同样的请求了那么就直接分发响应 mDelivery.postResponse(request, response); } } }
-
4.1.1 mCache.get 其中mCache对应DiskBaseCache
public synchronized Entry get(String key) { CacheHeader entry = mEntries.get(key); // 如果缓存内容不存在返回null if (entry == null) { return null; } // 内部根据key获取了文件名然后创建了一个File对象,创建文件名的规格是把key平均分成两部分,分别计算 // hash然后拼接在一起,感觉是为了提高不重复的概率 File file = getFileForKey(key); try { CountingInputStream cis = new CountingInputStream( new BufferedInputStream(createInputStream(file)), file.length()); try { // 再读一遍防止文件名与缓存内容对不上 CacheHeader entryOnDisk = CacheHeader.readHeader(cis); // 不相等的情况有两个缓存文件互相名字交换等情况 if (!TextUtils.equals(key, entryOnDisk.key)) { // 从Map中移除,返回null removeEntry(key); return null; } // 读取响应体 byte[] data = streamToBytes(cis, cis.bytesRemaining()); // 将响应头与响应体进行组合返回一个Entry return entry.toCacheEntry(data); } finally { cis.close(); } } catch (IOException e) { // 读取失败也从Map中移除,并且将缓存文件删除,返回null remove(key); return null; } }
-
4.1.1.1 getFileForKey 根据缓存key创建File对象 getFilenameForKey根据缓存key获取缓存文件名
public File getFileForKey(String key) { return new File(mRootDirectory, getFilenameForKey(key)); } private String getFilenameForKey(String key) { int firstHalfLength = key.length() / 2; String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode()); localFilename += String.valueOf(key.substring(firstHalfLength).hashCode()); return localFilename; }
-
4.1.1.2 entry.toCacheEntry 将缓存的响应头与缓存的响应体进行组合
Entry toCacheEntry(byte[] data) { Entry e = new Entry(); e.data = data; e.etag = etag; e.serverDate = serverDate; e.lastModified = lastModified; e.ttl = ttl; e.softTtl = softTtl; e.responseHeaders = HttpHeaderParser.toHeaderMap(allResponseHeaders); e.allResponseHeaders = Collections.unmodifiableList(allResponseHeaders); return e; }
-
4.1.1.2.1 HttpHeaderParser.toHeaderMap
static Map<String, String> toHeaderMap(List<Header> allHeaders) { // 创建一个大小写不敏感的TreeMap也就是忽略大小写,比如Expired与expired会被认为相同 Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); // 后面的响应头具有高优先级 for (Header header : allHeaders) { headers.put(header.getName(), header.getValue()); } return headers; }
-
4.1.2 request.parseNetworkResponse 来看看StringRequest是怎么执行的
// 从请求头中读出charset然后把响应体转化为一个字符串 protected Response<String> parseNetworkResponse(NetworkResponse response) { String parsed; try { parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); }
-
4.1.3 HttpHeaderParser.parseCacheHeaders 该方法会返回一个Cache.Entry,如果调用parseNetworkResponse的是NetworkDispatcher的话会把这个Cache.Entry写入缓存文件,如果是CacheDispatcher调用的话那么基本没什么用
public static Cache.Entry parseCacheHeaders(NetworkResponse response) { long now = System.currentTimeMillis(); Map<String, String> headers = response.headers; long serverDate = 0; long lastModified = 0; long serverExpires = 0; long softExpire = 0; long finalExpire = 0; long maxAge = 0; long staleWhileRevalidate = 0; boolean hasCacheControl = false; boolean mustRevalidate = false; String serverEtag = null; String headerValue; headerValue = headers.get("Date"); if (headerValue != null) { // 将服务器返回的处理请求的时间转化为long serverDate = parseDateAsEpoch(headerValue); } headerValue = headers.get("Cache-Control"); if (headerValue != null) { hasCacheControl = true; String[] tokens = headerValue.split(",", 0); for (int i = 0; i < tokens.length; i++) { String token = tokens[i].trim(); // 不使用缓存直接返回null if (token.equals("no-cache") || token.equals("no-store")) { return null; } else if (token.startsWith("max-age=")) { try { // 缓存的内容将在maxAge秒后失效 maxAge = Long.parseLong(token.substring(8)); } catch (Exception e) { } // 在staleWhileRevalidate内可以直接把缓存内容返回 } else if (token.startsWith("stale-while-revalidate=")) { try { staleWhileRevalidate = Long.parseLong(token.substring(23)); } catch (Exception e) { } // 本地副本过期前,可以使用本地副本;本地副本一旦过期,必须服务器进行有效性校验。 } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { mustRevalidate = true; } } } headerValue = headers.get("Expires"); if (headerValue != null) { // 获取服务器设置的缓存过期时间 serverExpires = parseDateAsEpoch(headerValue); } headerValue = headers.get("Last-Modified"); if (headerValue != null) { // 记录一下服务端该资源的最后修改时间 lastModified = parseDateAsEpoch(headerValue); } // 获取ETag serverEtag = headers.get("ETag"); // 缓存控制头优先于Expires,因为前者限制性更强 if (hasCacheControl) { // 软过期,不怎么理解怎么把缓存当做当前响应返回时间了 softExpire = now + maxAge * 1000; // 该响应的最终过期时间 finalExpire = mustRevalidate ? softExpire : softExpire + staleWhileRevalidate * 1000; } else if (serverDate > 0 && serverExpires >= serverDate) { // 如果没有缓存控制头那么根据expires决定 softExpire = now + (serverExpires - serverDate); finalExpire = softExpire; } Cache.Entry entry = new Cache.Entry(); entry.data = response.data; entry.etag = serverEtag; entry.softTtl = softExpire; entry.ttl = finalExpire; entry.serverDate = serverDate; entry.lastModified = lastModified; entry.responseHeaders = headers; entry.allResponseHeaders = response.allHeaders; return entry; }
-
4.1.4 mWaitingRequestManager.maybeAddToWaitingRequests mWaitingRequestManager为WaitingRequestManager的实例
private synchronized boolean maybeAddToWaitingRequests(Request<?> request) { String cacheKey = request.getCacheKey(); // 如果已经有相同缓存key的请求了并且该请求还没收到响应那么加入到List后面,防止相同缓存key的请求一个还没得到响应下一个就又发出请求了 if (mWaitingRequests.containsKey(cacheKey)) { List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); if (stagedRequests == null) { stagedRequests = new ArrayList<>(); } request.addMarker("waiting-for-response"); stagedRequests.add(request); mWaitingRequests.put(cacheKey, stagedRequests); if (VolleyLog.DEBUG) { VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); } return true; } else { // 插入null,标志着当前的request正在等待响应 mWaitingRequests.put(cacheKey, null); // 设置监听以便在网络请求完成后把Request从mWaitingRequest中移除 request.setNetworkRequestCompleteListener(this); if (VolleyLog.DEBUG) { VolleyLog.d("new request, sending to network %s", cacheKey); } return false; } }
-
4.1.3 mDelivery.postResponse mDelivery是一个ExecutorDelivery实例,如果是SoftTtl过期Ttl没过期则可能会分发两次响应,一次属于缓存,一个属于网络请求,但是如果网络请求是304则只会分发属于缓存的那次响应,续文会讲到
@Override public void postResponse(Request<?> request, Response<?> response, Runnable runnable) { request.markDelivered(); request.addMarker("post-response"); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); } mResponsePoster = new Executor() { @Override public void execute(Runnable command) { handler.post(command); } }; // 也就是在主线程调用ResponseDeliveryRunnable.run public void run() { // 取消了就不分发了直接结束 if (mRequest.isCanceled()) { mRequest.finish("canceled-at-delivery"); return; } // 成功分发成功,失败分发失败 if (mResponse.isSuccess()) { mRequest.deliverResponse(mResponse.result); } else { mRequest.deliverError(mResponse.error); } // softTtl过期Ttl不过期那么还需要执行mRunnable其实就是把Request加入到网络请求队列中去 if (mResponse.intermediate) { mRequest.addMarker("intermediate-response"); } else { mRequest.finish("done"); } if (mRunnable != null) { mRunnable.run(); } }
-
4.1.3.1 mRequest.deliverResponse 该方法是一个抽象方法 用于分发响应 看看StringRequest怎么实现的,内部就是直接回调Listener.onResponse
protected void deliverResponse(String response) { Response.Listener<String> listener; synchronized (mLock) { listener = mListener; } if (listener != null) { listener.onResponse(response); } }
-
4.1.3.2 mRequest.deliverError StringRequest没有重写该方法,内部就是调用ErrorListener.onErrorResponse()
public void deliverError(VolleyError error) { Response.ErrorListener listener; synchronized (mLock) { listener = mErrorListener; } if (listener != null) { listener.onErrorResponse(error); } }
至此CacheDispatcher的流程结束,下文主要讲NetworkDispatcher
第一部分总结
- 常规用法会创建一个缓存线程四个网络请求线程,共5个线程
- 文件缓存内容前后顺序是magic、key、eTag、serverDate、lastModified、ttl、softTtl、所有响应头、响应正文
- 对于缓存文件是否过期,通过ttl和softTtl,并且ttl >= softTtl, 不相等的情况只有缓存控制头有stale-while-revalidate=和must-revalidate这两个字段
- 一个请求可能会有两个响应,当Ttl没过期,SoftTtl过期了就会先把缓存的内容返回然后再去请求网络,如果不是304则再次回调Request.deliverResponse
- 对于已经过期的缓存,就放到网络请求队列中去