Volley
[TOC]
Volley,Okhttp,Rerofit的三者区别
- Voller底层封装的是HttpUrlConnection,支持图片加载,网络请求排序,优先级处理,缓存,与Activity生命周期联动。扩展性好,支持httpclient,HttpUrlConnection,OkHttp,在频繁请求和加载数据量少的时候优势,不适合大数据加载,Request和Response都是使用byte数组存储数据,大数据=大数组,消耗内存。
- Okhttp底层基于原生http,支持异步同步,缓存相应数据,减少重复请求,自动重连,支持GZIP减少数据流量。请求,处理速度快,基于NIO和Okio。NIO是非阻塞式的,Okio是Square基于IO,NIO的一个高效处理数据流的开源库。API使用更加方便,简单,适用于数据大的重量级网络请求。
- Retrofit基于Okhttp,通过注解的方式配置请求,序列化方式丰富,提供Rxjava支持。请求处理速度最快,扩展性太差,封装太好。使用最简单,代码最少,解耦更加彻底,易与其他框架联用。任何场景开发优先使用,如序列化方式多,项目中使用Rxjava
Volley的简单使用
RequestQueue mqueue = Volley.newRequestQueue(context); //创建请求 StringRequest request = new StringRequest(GET, "http://www.baidu.com", new Response.Listener<String>() { @Override public void onResponse(String response) { } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }); //添加请求到队列中 mqueue.add(request);
讯享网
以上就是volley最简单的使用,更多具体的应用可以参考Volley使用
Volley流程分析
通过上面volley的简单使用,逐步跟踪分析Volley网络访问的流程。
请求队列的创建
volley的使用首先通过Volley类创建一个请求队列RequestQueue
讯享网 RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
Volley类
volley类中有4个静态重载方法:
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) public static RequestQueue newRequestQueue(Context context, int maxDiskCacheBytes) public static RequestQueue newRequestQueue(Context context, HttpStack stack) public static RequestQueue newRequestQueue(Context context)
最终调用都是第一个方法,具体实现如下:
讯享网public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) { //创建缓存文件 File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); //创建useragent,useragent网络请求时的一个请求头 String userAgent = "volley/0"; try { String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); userAgent = packageName + "/" + info.versionCode; } catch (NameNotFoundException e) { } //stack是真正去跟网络打交道的类,他们都继承HttpStack接口,文章后续会介绍 //如果使用者自定义请求类,则通过sdk的版本创建相应的stack if (stack == null) { //SDK版本大于等于9(>=2.3),使用HurlStack,基于HttpUrlConnection的实现 if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { //如果小于9,则是用HttpClient来实现,基于HttpClient的实现 stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } //创建BasicNetwork对象,并将HttpStack封装在Network中 //通过Network去管理HttpStack方法的调用 Network network = new BasicNetwork(stack); RequestQueue queue; //创建请求队列, if (maxDiskCacheBytes <= -1) { // 创建基于Disk的缓存对象,默认缓存为5M queue = new RequestQueue(new DiskBasedCache(cacheDir), network); } else { // Disk cache size specified queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network); } //请求队列启动 queue.start(); return queue; }
通过上述的分析,创建请求队列的过程中,主要做了以下几件事:
1)创建缓存文件和UserAgenp字符串
2)根据SDK版本来创建HttpStack的实现,如果是2.3以上的,则使用基于HttpUrlConnection实现的HurlStack,反之,则利用HttpClient实现的HttpClientStack。
3)创建一个BasicNetwork对象,并将HttpStack封装在Network中
4)创建一个DiskBasedCache对象,和Network一起,传给RequestQueue作为参数,创建RequestQueue对象。
5)调用 RequestQueue的 start 方法,然后返回创建的queue对象。
【扩展】HttpURLConnection 和 AndroidHttpClient(HttpClient 的封装)如何选择及原因:
- 在 2.2之前,HttpURLConnection 有个重大 Bug,调用 close() 函数会影响连接池,导致连接复用失效,所以在 2.2之前使用 HttpURLConnection 需要关闭 keepAlive。
- 在 2.3 HttpURLConnection 默认开启了 gzip 压缩,提高了 HTTPS 的性能。
- 在4.0 HttpURLConnection 支持了请求结果缓存。 再加上 HttpURLConnection 本身 API 相对简单,所以对 Android 来说,在 2.3 之后建议使用 HttpURLConnection,之前建议使用 AndroidHttpClient。
【扩展】关于 User Agent
- 通过代码我们发现如果是使用 AndroidHttpClient,Volley 还会将请求头中的 User-Agent 字段设置为 App 的 packageName/versionCode,如果异常则使用 "volley/0"
- 对于 HttpURLConnection, 通过数据抓包会发现,HttpURLConnection 默认是有 User-Agent 的,类似:
Dalvik/1.6.0 (Linux; U; Android 4.1.1; Google Nexus 4 - 4.1.1 - API 16 - 768x1280_1 Build/JRO03S)
实际在请求发出之前,会检测 User-Agent 是否为空,如果不为空,则加上系统默认 User-Agent。在 Android 2.1 之后,可以通过
讯享网String userAgent = System.getProperty("http.agent");
得到系统默认的 User-Agent,Volley 如果希望自定义 User-Agent,可在自定义 Request 中重写 getHeaders() 函数
@Override public Map<String, String> getHeaders() throws AuthFailureError { // self-defined user agent Map<String, String> headerMap = new HashMap<String, String>(); headerMap.put("User-Agent", "android-open-project-analysis/1.0"); return headerMap; }
RequestQueue类
RequestQueue的创建
首先是RequestQueue的创建。RequestQueue类有三个构造函数,
讯享网//最终调用的构造函数 public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { //缓存 mCache = cache; mNetwork = network; //实例化网络请求线程数组,数组大小默认是4 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); }
RequestQueue在构造函数中初始化了4个成员变量:
- mCache(文件缓存)
- mNetwork(BasicNetwork实例)
- mDispatchers(网络请求线程数组)
- mDelivery(请求结果分发器)
同时,在RequestQueue中还维护了4个集合:
- 基于优先级的 Request 队列,缓存请求队列和网络请求队列
通过take方法会取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的对象被加入为止 。
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();讯享网
private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();
- 一个正在进行中,尚未完成的请求集合
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>(); - 一个等待请求的集合,如果一个请求正在被处理并且可以被缓存,后续的相同 url 的请求,将进入此等待队列。
讯享网
private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>();
RequestQueue.start()
RequestQueue实例化完成后,最后调用了它的start()方法
public void start() { // 保证所有正在运行的Dispatcher(也就是线程)都停止 stop(); // 创建缓存调度线程,并启动线程。 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(); } } //停止缓存线程跟所有的网络线程 public void stop() { if (mCacheDispatcher != null) { mCacheDispatcher.quit(); } for (int i = 0; i < mDispatchers.length; i++) { if (mDispatchers[i] != null) { mDispatchers[i].quit(); } } }
start()方法中做了以下几件事:
- 先见缓存线程和所有网络线程停止
- 然后重新创建缓存线程并开启,并将 mCacheQueue,mNetworkQueue, mCache 和 mDelivery传入
- 默认创建4个网络请求线程并开启,并将 mCacheQueue,mNetwrok, mCache 和 mDelivery传入
- 最后线程都开启后,就静静的等待请求的到来
网络请求的创建
Volley为我们提供了多种的网络请求,比如StringRequest、JsonObjectRequest、ImageRequest等等。它们都继承抽象类Request,所以如果需要自定义一个网路请求,继承Request类,并实现其相应的抽象的方法即可。
在抽象类Request中定义一些基本的参数变量,如:
- 请求方式
讯享网
public interface Method { int DEPRECATED_GET_OR_POST = -1; int GET = 0; int POST = 1; int PUT = 2; int DELETE = 3; int HEAD = 4; int OPTIONS = 5; int TRACE = 6; int PATCH = 7; } private final int mMethod; - 请求url
private final String mUrl; - 网络请求错误监听
讯享网
private Response.ErrorListener mErrorListener; - 是否需要缓存 默认可以缓存
private boolean mShouldCache = true; - 分类标签
讯享网
private Object mTag;
还有两个抽象方法,子类必须实现
//处理响应的数据 abstract protected Response<T> parseNetworkResponse(NetworkResponse response); //处理响应成功的回调 abstract protected void deliverResponse(T response)
我们以最简单的StringRequest为例,看看其具体的实现,而其他的Request类,大体的实现都差不多。
讯享网public class StringRequest extends Request<String> { //请求成功监听回调 private Listener<String> mListener; public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) { super(method, url, errorListener); mListener = listener; } public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) { this(Method.GET, url, listener, errorListener); } @Override protected void onFinish() { super.onFinish(); mListener = null; } @Override protected void deliverResponse(String response) { if (mListener != null) { //请求成功后,将处理结果String回调给成功监听器 mListener.onResponse(response); } } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { String parsed; try { //将响应包装成String类型 parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); } }
网络请求的创建,就是初始化一些基本参数,病假请求类型,url,成功错误回调监听封装起来,然后通过parseNetworkResponse()方法对响应的数据进行相应的处理,最后在deliverResponse()方法中回调给成功监听器。
最后生成返回的Response对象的时候需要传入一个Cache.Entry的缓存对象,它通过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; //服务器过期时间(http1.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) { serverDate = parseDateAsEpoch(headerValue); } //获取响应头中是否有缓存响应头cache-control 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(); //如果Cache-Control里为no-cache和no-store则表示不需要缓存,返回null if (token.equals("no-cache") || token.equals("no-store")) { return null; } else if (token.startsWith("max-age=")) { //缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用 try { maxAge = Long.parseLong(token.substring(8)); } catch (Exception e) { } } else if (token.startsWith("stale-while-revalidate=")) { //为过了缓存时间后还可以继续使用缓存的时间,即真正的缓存时间是“max-age=” + “stale-while-revalidate=”的总时间 try { staleWhileRevalidate = Long.parseLong(token.substring(23)); } catch (Exception e) { } } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { //有“must-revalidate”或者“proxy-revalidate”字段则过了缓存时间缓存就立即请求服务器 mustRevalidate = true; } } } //响应过期的日期和时间,是一个绝对时间 。Expires 的一个缺点就是,返回的到期时间是服务器端的时间, // 这样存在一个问题,如果客户端的时间与服务器的时间相差很大,那么误差就很大, // 所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代。 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"); //是否有Cache-Control头,且非no-cache和no-store方式 if (hasCacheControl) { //缓存新鲜度时间为当前的时间+cachecontrol中maxage的时间*1000(毫秒) softExpire = now + maxAge * 1000; //最终过期的时间先要判断是否有“must-revalidate”或者“proxy-revalidate”字段,如果有的话 //最终过期的时间跟软过期一致,否则为软过期时间+stale-while-revalidate字段的时间*1000 finalExpire = mustRevalidate ? softExpire : softExpire + staleWhileRevalidate * 1000; } else if (serverDate > 0 && serverExpires >= serverDate) { //如果响应头中没有cache-control头,且响应头中返回了过期时间 //存新鲜度时间等于当前时间加上(过期时间-返回的服务器时间) 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; }
从这个方法中,我们得知了缓存的过期时间以及缓存新鲜度时间是怎么来的。缓存相关字段:
- Date:返回服务器时间,如果想得到服务器的时候,我们可以从这里获取
- Cache-Control:为no-cache和no-store:不缓存响应数据,如果需要缓存响应数据,当需要设置缓存时,通过maxAge的值来设置缓存过期的时间。
- Must-revalidate和proxy-revalidate:该值为一个boolean值,服务器告诉客户端,缓存数据过期前,可以使用缓存;缓存一旦过期,必须去源服务器进行有效性校验。
- Expires:设置缓存过期的时间,如果 Cache-Control设置为需要缓存,那么优先以 Cache-Control的maxAge的值来设置缓存过期时间。
- Last-Modified:在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是客户端请求的资源,同时有一个Last-Modified的属性标记此文件在服务器端最后被修改的时间。
客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器传送If-Modified-Since报头,询问该时间之后文件是否有被修改过,如果服务器端的资源没有变化,则自动返回 HTTP 304(Not Changed.)状态码,内容为空,这样就节省了传输数据量。它和请求头的if-modified-since字段去判断资源有没有被修改的。 - ETags:它和if-None-Match(HTTP协议规格说明定义ETag为“被请求变量的实体值”,或者是一个可以与Web资源关联的记号)常用来判断当前请求资源是否改变。类似于Last-Modified和HTTP-IF-MODIFIED-SINCE。但是有所不同的是Last-Modified和HTTP-IF-MODIFIED-SINCE只判断资源的最后修改时间,而ETags和If-None-Match可以是资源任何的任何属性,不如资源的MD5等
添加请求
请求队列,网络请求都创建完成后,最后将网络添加进入请求队列即可。现在就开始分析,RequestQueue的add()方法。
讯享网 public <T> Request<T> add(Request<T> request) { //标记该请求由当前的RequestQueue处理 request.setRequestQueue(this); synchronized (mCurrentRequests) { //将请求放入尚未完成的请求集合中保存起来 mCurrentRequests.add(request); } //获得当前请求的序号 request.setSequence(getSequenceNumber()); //添加log日志tag request.addMarker("add-to-queue"); //判断当前请求是否支持缓存,如果请求不能缓存,直接添加到网络请求队列 //默认是可以缓存 if (!request.shouldCache()) { mNetworkQueue.add(request); return request; } //如果支持缓存,锁定当前代码块,只能有一个线程执行 synchronized (mWaitingRequests) { //获取缓存key:mMethod + ":" + mUrl String cacheKey = request.getCacheKey(); //等待请求集合中是否有相同的请求 if (mWaitingRequests.containsKey(cacheKey)) { // 如果有相同请求,那么把这个请求放进mWaitingRequest中,等待. Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); //相同的请求,放入同一个LinkedList中 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 { //没有相同的请求,那么把请求放进mWaitingRequests中,同时也放进mCacheQueue缓存队列中 //这代表这个请求已经开始在缓存线程中运行了 mWaitingRequests.put(cacheKey, null); mCacheQueue.add(request); } return request; } }
通过上面代码的分析,add()方法主要做了以下几件事:
- 将当前的请求放入到当前的请求队列mCurrentRequests中去
- 如果当前请求不缓存,直接将请求添加到网络请求队列mNetworkQueue中去
- 如果支持缓存并且存在相同请求,则将该请求放入等待请求集合mWaitingRequests中去,排队等待
- 如果没有相同请求,则直接放入缓存队列mCacheQueue中去
后台运行的缓存调度线程和网络调度线程会一直不断的轮询各自的请求队列,所以每当将请求放入到缓存队列和网络请求队列中,各自线程就会发现请求任务并开始处理。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/68535.html