Volley是一个网络请求的库,固然如今你们都用OkHttp或者Retrofit, 写这篇博客目的是想自定义一个线程模型处理网路请求,Volley比较简单并且采用了面向接口的设计,拿来做参考,顺便把分析作一下记录。php
咱们须要建立一个RequestQueue queue,若是项目中的网路请求比较少的话那推荐用一个queue,在Application中对它进行初始化,若是有频繁的网络请求能够在每一个Activity里面建立一个queue。在作网络请求的时候只须要这样:先建立一个RequestQueue,再建立一个Request,能够是StringRequest,也能够是JsonRequest,而后调用RequestQueue对象的add方法,把刚建立的Request对象添加到队列就能够了。
以StringRequest为例html
RequestQueue queue = Volley.newRequestQueue(context); StringRequest request = new StringRequest("http://www.baidu.com", new Response.Listener<String>() { @Override public void onResponse(String response) { Log.i("VolleyRequest", response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.i("VolleyRequest", error.getMessage()); } }); queue.add(request);
下面是主要的类关系图
先来看RequestQueue,下面这行代码会建立一个RequestQueuejava
Volley.newRequestQueue(context);
Volley.java是一个初始化RequestQueue的类,它有两个重载的方法android
public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, null); } public static RequestQueue newRequestQueue(Context context, HttpStack stack) { ... 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; }
最终都是经过new RequestQueue(new DiskBasedCache(cacheDir), network)建立了一个RequestQueue,能够看到在初始化了queue以后立刻调用了start方法,因此在使用的时候不须要显示调用queue的start方法。另一点它会根据编译版本的API选择不一样的网络实现类,若是API小于9就是用Apache的HttpClient,不然使用基于HttpURLConnection的HurlStack。这个Network等会再说,先看RequestQueue,它有两个带有优先级的阻塞式队列:数组
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>(); private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();
mCacheQueue是存放的缓存过的请求队列,mNetworkQueue是须要从网络获取数据的请求队列,这两个队列都是PriorityBlockingQueue,与普通的阻塞式队列相比它要求里面的元素必须实现Comparable接口或者在构造队列的时候须要传一个实现了 Comparator接口的比较器。它是按照待比较对象的降序排列的,也就是说在比较的时候小的会比大的优先级高。Request实现了Comparable接口,看它的compare方法:缓存
@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(); }
Priority是一个常量类,有四个值从小到大分别是LOW,NORMAL,HIGH和IMMEDIATE。每次在调用getPriority()取值的时候会返回指定值:默认是返回NORMAL,ImageRequest是返回LOW。这样的话有一个ImageRequest imageRequest先进队列接着又进来一个StringRequest stringRequest,stringRequest跟imageRequest相比,后者getPriority()返回LOW而前者返回NORMAL取ordinary时后者小于前者,在执行stringRequest.compareTo(imageRequest)时返回负数,那么StringRequest会先出队列。若是是相同类型的Request那么就比较它们的mSequence值,mSequence是在RequestQueue的add方法里被赋值的服务器
public <T> Request<T> add(Request<T> request) { ... // Process requests in the order they are added. request.setSequence(getSequenceNumber()); request.addMarker("add-to-queue"); ... }
再看getSequenceNumber()方法:网络
public int getSequenceNumber() { return mSequenceGenerator.incrementAndGet(); }
这个mSequenceGenerator是一个AtomicInteger,每次调incrementAndGet()它会自增1,这样每个刚加入的Request的mSequence值都会比上一个Request高,在调用compareTo方法比较的时候越日后的Request就越大,这样越早加入的Request的优先级就越高,实际上就是维护了一个FIFO的Request队列,保证先来的请求会优先被处理。
再看一下它的构造方法,总共有三个:异步
public RequestQueue(Cache cache, Network network) { this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); } public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; }
两个重载的方法最终调用了4个参数的构造方法初始化了它的4个成员变量,其中mNetwork就是上面Volley在初始化队列时会区分API版本的参数,这4个参数分别什么含义呢ide
/** Cache interface for retrieving and storing responses. */ private final Cache mCache; /** Network interface for performing requests. */ private final Network mNetwork; /** The network dispatchers. */ private NetworkDispatcher[] mDispatchers; /** Response delivery mechanism. */ private final ResponseDelivery mDelivery;
四个参数分别对应了它的缓存策略,网络请求实现,网路请求调度器和执行结果的传递者。
它是一个接口,它的实现类是DiskBasedCache,它会以请求的URL做为Key(不是只把URL当作key),请求结果的byte数组为Value存储请求结果,它默认是5M的存储空间,若是不够了它会把先缓存的数据给删掉,再存新数据,同时会缓存请求的Header信息而且根据header的信息判断缓存是否过时。Cache的实现类是DiskBasedCache,它有一个内部类CacheHeader,成员跟Cache的内部类Entry基本一致,存放的是请求结果的Header信息,与Entry相比少了一个byte数组data,data是存放请求结果的,它只存Header因此不须要结果,多了一个key,用来标识缓存的位置,还多了一个size,用来标识Header的大小。DiskBasedCache还有一个LinkedHashMap类型的的成员mEntries,它以String为键以CacheHeader为值保存请求的信息,这样每次在须要对缓存作IO操做的时候取它的值来比较,避免了频繁的IO操做提高了效率。
它也是一个接口,它的实现类是BasicNetwork,Volley用它来进行网路操做,可是实际执行又对它作了一次封装,具体网络实现依赖于它的成员HttpStack
public interface Network { public NetworkResponse performRequest(Request<?> request) throws VolleyError; } public class BasicNetwork implements Network { @Override public NetworkResponse performRequest(Request<?> request) throws VolleyError { ... while (true) { HttpResponse httpResponse = null; byte[] responseContents = null; httpResponse = mHttpStack.performRequest(request, headers); return new NetworkResponse(statusCode, responseContents, responseHeaders, false, SystemClock.elapsedRealtime() - requestStart); } ... } } }
能够看到BasicNetwork在调用performRequest时是经过调用mHttpStack.performRequest来拿到数据,这个HttpStack也是一个接口(Volley的设计给了开发者充分自定义的空间),这就表示开发者可使用自定义的HttpStack来进行网络操做
public interface HttpStack { public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError; }
它的实现类有两个:使用了Apache的HttpClientStack和基于HttpURLConnection的HurlStack,这两个也是开发者初次接触网络请求会遇到的,至于应该用哪一个能够参考使用HttpURLConnection仍是HttpClient
这是一个线程,里面会关联一个请求队列mQueue和一个网络处理类mNetwork,用于对Volley的请求作调度,它会在run方法里面会执行一个死循环从mQueue里挨个取出Request而后调用mNetwork的performRequest方法作处理,最后把结果经过ResponseDelivery传递给主线程。默认会开启4个线程,看RequestQueue代码,DEFAULT_NETWORK_THREAD_POOL_SIZE会做为参数传递给构造方法,这个mDispatchers是一个线程数组,能够对每一线程作调度,尽管没有用ThreadPoolExecutor,可是也就至关于一个线程池,线程池的目的就是为了能够对每个线程作调度重用线程已有线程。
/** 要启动的网络请求调度线程个数 */ private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; }
毫无疑问这也是一个接口,它的实现类是ExecutorDelivery,网络操做是耗时的须要在子线程中完成,可是结果是在主线程经过Listener拿到的,那怎么办,确定是经过Handler了,一个关联了主线程Looper的Handler
public ExecutorDelivery(final Handler handler) { mResponsePoster = new Executor() { @Override public void execute(Runnable command) { handler.post(command); } }; }
若是有请求结果须要传递给主线程就会执行一个新的Runnable,任务的执行是在主线程完成。但在使用的时候并无要求开发者须要传递一个这样的Handler,是怎么回事,看RequestQueue的构造方法
public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); }
也就明白了,它在初始化queue的时候就同时把这个Handler给初始化了。
下面来看它的执行过程,由主线程发起,交给子线程处理,最后把结果经过回调传递给主线程
任务的发起从RequestQueue的start方法开始
public void start() { stop(); // 把当前线程中止 // 建立一个缓存调度线程并启动 mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); mCacheDispatcher.start(); // 建立4个网络请求调度线程 for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } }
首先会调用stop方法把线程给停掉,而后再初始化新的线程执行任务。这里要额外说一下它在初始化NetworkDispatcher的同时还初始化了一个CacheDispatcher,这个CacheDispatcher也是一个线程,Volley是自带缓存功能的,这个线程就是负责对已经缓存的请求作调度
线程开启了会作哪些事情呢,先看缓存调度线程
public class CacheDispatcher extends Thread { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 初始化缓存,会在这里建立缓存目录 mCache.initialize(); // 执行一个死循环轮询mCacheQueue while (true) { try { // 从缓存的请求队列里取一个请求,若是没有当前线程会被阻塞直到拿到一个能够用的 final Request<?> request = mCacheQueue.take(); // 添加标记,过程当中会添加不少标记都是为了在Logcat中显示标记信息 request.addMarker("cache-queue-take"); // 若是请求被取消了就再也不对结果进行分发 if (request.isCanceled()) { request.finish("cache-discard-canceled"); continue; } // Cache.Entry是用来保存请求结果的 // 尝试从缓存中恢复 Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) { request.addMarker("cache-miss"); // 若是根据URL获得的Entry为空就把这个请求放到须要从网络获取数据的mNetworkQueue里 mNetworkQueue.put(request); continue; } // Entry不为空可是已经失效(经过验证ttl)也把这个请求放到须要从网络获取数据的mNetworkQueue里 if (entry.isExpired()) { request.addMarker("cache-hit-expired"); request.setCacheEntry(entry); mNetworkQueue.put(request); continue; } // 拿到了缓存数据 request.addMarker("cache-hit"); Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); // 是否须要更新(经过验证softTtl) if (!entry.refreshNeeded()) { // 若是不须要更新就把结果分发给用户 mDelivery.postResponse(request, response); } else { // 须要更新 request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); // 添加一个标记 response.intermediate = true; // 这时候能够把缓存数据分发给用户但仍是得再加入到网络请求队列中 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; } } } }
每一步操做都加了注释应该很清楚,另外有两点须要说明,第一点就是开始的
` Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
`这条语句,它是设置当前线程为后台线程,不然尽管是工做线程但它会拥有跟UI线程相同的的抢夺CPU资源的能力,跟UI线程的优先级是同样的,这就可能致使UI线程得到的CPU时间较短形成界面的卡顿,而调用这条语句之后系统就会知道它是属于background group的,最多只分给它5%-10%的时间片,从而保障UI线程能够获取更多的CPU时间。第二点是它缓存的失效时间,缓存是否须要刷新是怎么控制的
/** 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的两个方法,经过跟当前时间作对比,那结果就是看ttl和softTtl了,它们又是在哪被赋值的呢,是经过调用HttpHeaderParser的静态方法parseCacheHeaders解析请求结果获得的
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; // 下面这些都是Http协议里规定的字段 headerValue = headers.get("Date"); if (headerValue != null) { serverDate = parseDateAsEpoch(headerValue); } headerValue = headers.get("Cache-Control"); if (headerValue != null) { hasCacheControl = true; String[] tokens = headerValue.split(","); for (int i = 0; i < tokens.length; i++) { String token = tokens[i].trim(); if (token.equals("no-cache") || token.equals("no-store")) { return null; } else if (token.startsWith("max-age=")) { try { maxAge = Long.parseLong(token.substring(8)); } catch (Exception e) { } } 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); } serverEtag = headers.get("ETag"); // Cache-Control takes precedence over an Expires header, even if both exist and Expires // is more restrictive. if (hasCacheControl) { softExpire = now + maxAge * 1000; finalExpire = mustRevalidate ? softExpire : softExpire + staleWhileRevalidate * 1000; } else if (serverDate > 0 && serverExpires >= serverDate) { // Default semantic for Expire header in HTTP specification is softExpire. 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; return entry; }
它同时使用了Expires和max-age来计算时间,是由于Expires在HTTP/1.0中已经定义,Cache-Control:max-age在HTTP/1.1中才有定义,为了向下兼容,仅使用max-age不够。能够看到ttl值和softTtl值都是在当前时间的基础上加上了解析header里面的值,因此在缓存调度的时候缓存有效时间就是maxAge * 1000,缓存更新时间就是转换后的headers.get("Expires")时间。在缓存的调度里面只是取缓存那么缓存文件是在何时被建立的呢,这就得看它的网络调度了,下面是NetworkDispatcher
public class NetworkDispatcher extends Thread { @Override public void run() { // 也是先把线程设置为background group Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { long startTimeMs = SystemClock.elapsedRealtime(); Request<?> request; try { // 在缓存调度时就是把请求加到了这个队列里面,它是在RequeQueue里被初始化的 request = mQueue.take(); } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } try { request.addMarker("network-queue-take"); // 若是请求被取消了就再也不处理 if (request.isCanceled()) { request.finish("network-discard-cancelled"); continue; } addTrafficStatsTag(request); // 终于到了它的网络执行了 // 这里在前面已经介绍过了,具体的网络操做是由mHttpStack负责 NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete"); // 当请求结果状态码为304时,NetworkResponse的notModified会被赋值为true // 当服务器返回304而且结果已经被传递过就终止此次请求 if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); continue; } // 解析Header信息更新ttl和softTtl值 Response<?> response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); // 就是在这里加入了缓存 // 若是须要缓存而且该请求结果的Header信息已经被解析过 if (request.shouldCache() && response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } // 将结果返回 request.markDelivered(); mDelivery.postResponse(request, response); } catch (VolleyError volleyError) { volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); parseAndDeliverNetworkError(request, volleyError); } catch (Exception e) { VolleyLog.e(e, "Unhandled exception %s", e.toString()); VolleyError volleyError = new VolleyError(e); volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); mDelivery.postError(request, volleyError); } } } }
该线程会轮询mNetworkQueue挨个取出request,而后调用Network的performRequest联网取数据,结果会被封装成一个Response交由ExecutorDelivery将结果传递给主线程。NetworkDispatcher和CacheDispatcher共享了同一个mNetworkQueue减小了线程间数据传递的维护成本。这里有一点我还不是很清楚,缓存在加入时会检查缓存文件是否已到达上限,若是是的话会一直删除直到达到指定条件,下面是DiskBasedCache
private void pruneIfNeeded(int neededSpace) { if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) { return; } ... 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; } } ... }
删除终止的条件是((mTotalSize + neededSpace) < mMaxCacheSizeInBytes HYSTERESIS_FACTOR),就是:当前缓存空间大小 + 须要的缓存空间大小 < 缓存总空间大小 HYSTERESIS_FATOR,这个HYSTERESIS_FATOR是在该类被初始化的时候就被初始化的private static final float HYSTERESIS_FACTOR = 0.9f;
为何是0.9我猜是一个经验值,删的太多会致使缓存的命中率下降,删的少了这个方法就得频繁执行。(有待验证)
最后看下结果是怎么处理的,下面是ExecutorDelivery
private class ResponseDeliveryRunnable implements Runnable { ... @Override 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(); } }
它是ExecutorDelivery的一个内部类,结果的传递都是经过调用ResponseDelivery的postResponse来完成,postResponse方法里面会在主线程执行一个Runnable,在这个Runnable里面作了什么事情呢,调用了Request的deliverResponse和deliverError方法,它们各自携带的参数最后传递给了Request里面的Listener,因此在主线程里面能够经过监听Listener来拿到网络操做的异步执行结果。好了,整个过程就分析结束了。
已经介绍过了Volley的网络操做能够本身决定实现者,因此能够用OKHttp做为Volley的HttpStack
具体使用能够参考 使用OKHttp处理Volley的底层HTTP请求