Android网络框架源码分析一----Volley
2016-07-06 23:39
489 查看
Android网络框架源码分析一----Volley
前言
公司最近新起了一个项目,对喜欢尝鲜的我们来说,好处就是我们可以在真实的项目中想尝试一些新技术,验证想法。新项目对网络框架的选取,我们存在三种方案:1.和我们之前的项目一样,使用
Loader + HttpClient + GreenDao + Gson + Fragment,优点是可定制性强,由于使用
Loader和
LoaderManager,代码健壮性强。
缺点是整套代码学习成本较高,使用过程中样板代码较多,(比如每一个Request都需要产生一个新类)
2.
Volley,作为
IO大会上得瑟过的一个网络库,其实不算什么新东西(2013
IO发布),使用较为简单,请求可以取消,可以提供优先级请求,看起来还是不错的。
3.
Retrofit,一款为了使请求极度简单化的
REST API Client,呼声也很高,使用门槛几乎是小白型。
如何选择呢?首先干掉1,因为对新人的学习成本确实太高,如果要快速开发一个项目,高学习成本是致命的,同时使用起来样板代码很多。
那么如何在
Volley和
Retrofit中选择呢?尽管网上有很多文章在介绍两个框架的使用方法,而对于其原理,特别是对比分析较少,如果你手里有一个项目,如何选择网络模块呢?
这里将分两篇文章从源码的角度对比分析这两个开源框架,希望能对你有所帮助。这是上篇,下篇Retrofit在这里,其中有干货总结哦~。
需要注意的是,这两篇文章并不会是一个入门使用的帮助文档,建议你在看本文的时候,最好看过官方文档和DEMO。
首先说明一下这两个网络框架在项目中的层次:
Paste_Image.png
从上图可知,不管
Volley还是
Retrofit,它们都是对现有各种方案进行整合,并提供一个友好,快速开发的方案,在整合过程中,各个模块都可以自行定制
或者替换。比如反序列化的工作,再比如HttpClient。
需要注意一点的是,这两个开源框架都没有实现
Http栈,
Http栈作为一个可替换的模块在两个框架中存在(Retrofit
2.0版本仅支持OkHttpClient)。Http栈在客户端常见的实现有
apache的
HttpClient和
square的
OkHttpClient,其中
apache HttpClient不开源,而
OkHttpClient是开源的,GitHub地址。
Volley 源码分析
Volley现在已经被官方放到
AOSP里面,已经逐步成为
Android官方推荐的网络框架。
类抽象
对Http协议的抽象
Requeset
顾名思义,对请求的封装,实现了
Comparable接口,因为在
Volley中是可以指定请求的优先级的,实现
Comparable是为了在
Request任务队列中进行排序,优先级高的
Request会被优先调度执行。
NetworkResponse
Http响应的封装,其中包括返回的状态码 头部 数据等。
Response
给调用者返回的结果封装,它比
NetworkResponse更加简单,只包含三个东西:数据 异常 和
Cache数据。
Network
对
HttpClient的抽象,接受一个
Request,返回一个
NetworkResponse
反序列化抽象
所谓反序列化,就是将网络中传输的对象变成一个
Java对象,
Volley中是通过扩展
Request类来实现不同的反序列化功能,如
JsonRequest StringRequest,我们也可以通过自己扩展一些
Request子类,来实现对请求流的各种定制。
请求工作流抽象
RequestQueue
用来管理各种请求队列,其中包含有4个队列
a) 所有请求集合,通过
RequestQueue.add()添加的
Request都会被添加进来,当请求结束之后删除。
b) 所有等待
Request,这是
Volley做的一点优化,想象一下,我们同时发出了三个一模一样的
Request,此时底层其实不必真正走三个网络请求,而只需要走一个请求即可。所以
Request1被
add之后会被调度执行,而
Request2和
Request3被加进来时,如果
Request1还未执行完毕,那么
Request2和
Request3只需要等着
Request1的结果即可。
c) 缓存队列,其中的
Request需要执行查找缓存的工作
d) 网络工作队列 其中的
Request需要被执行网络请求的工作
NetworkDispatcher
执行网络
Request的线程,它会从网络工作队列中取出一个请求,并执行。
Volley默认有四个线程作为执行网络请求的线程。
CacheDispatcher
执行
Cache查找的线程,它会从缓存队列中取出一个请求,然后查找该请求的本地缓存。
Volley只有一个线程执行
Cache任务。
ResponseDelivery
请求数据分发器,可以发布
Request执行的结果。
Cache
对
Cache的封装,主要定义了如何存储,获取缓存,存取依据
Request中的
getCacheKey()来描述。
提交请求
Volley通过
RequestQueue.add(Request)来往任务队列中增加请求:
Paste_Image.png
一个
Request被提交之后有几个去处:
1.
Set<Request<?>> mCurrentRequests对应所有请求队列。所有调用
add的
Request必然都会添加到这里面来。
2.
PriorityBlockingQueue<Request<?>> mNetworkQueue对应网络队列。如果一个
Request不需要缓存,那么add之后会被直接添加到网络队列中。
3.
PriorityBlockingQueue<Request<?>> mCacheQueue对应缓存请求。如果一个
Request需要缓存,并且当前的
RequestQueue中并没有一个
Request的
getCacheKey和当前
Request相同(可以认为一个请求),那么加入缓存队列,让缓存工作线程来处理。
4.
Map<String, Queue<Request<?>>> mWaitingRequests对应等待队列。如果
RequestQueue中已经有一个相同请求在处理,这里只需要将这个
Request放到等待队列中,等之前的
Request结果回来之后,进行处理即可。
Volley提交任务到队列中是不是很简单?下面来说说优先级请求的事情吧,你可能已经注意到了,上面两个存放需要执行任务的队列都是
PriorityBlockingQueue,前面说了
Request现实了
Comparable,看看这个方法:
@Override public int compareTo(Request<T> other) { Priority left = this.getPriority(); Priority right = other.getPriority(); //mSequence表示请求序列号,add时,会通过一个计数器来指定 return left == right ? this.mSequence - other.mSequence : right.ordinal() - left.ordinal(); }
所以,如果我们的工作线程(
NetworkDispatcher,CacheDispatcher)取任务时,自然会从头部开始取。
这里的优先级,仅仅是保证一个请求比另外一个请求先处理,而并不能保证一个高优先级请求一定会比低优先级的请求先回来
缓存工作线程处理
@Override public void run() { //初始化Cache mCache.initialize(); Request<?> request; while (true) { //阻塞 获取一个Cache任务 request = mCacheQueue.take(); try { //已经被取消 if (request.isCanceled()) { request.finish("cache-discard-canceled"); continue; } //如果拿cache未果,放入网络请求队列 Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) { request.addMarker("cache-miss"); mNetworkQueue.put(request); continue; } //缓存超时,放入网络请求队列 if (entry.isExpired()) { request.addMarker("cache-hit-expired"); request.setCacheEntry(entry); mNetworkQueue.put(request); continue; } //根据Cache构造Response Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); //是否超过软过期 if (!entry.refreshNeeded()) { // 直接返回Cache mDelivery.postResponse(request, response); } else { request.setCacheEntry(entry); //设置中间结果 response.intermediate = true; //发送中间结果 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. } } }); } } catch (Exception e) { } } }
这里可以看到
Volley确实对缓存封装很到位,各种情况都考虑到了,其中比较重要的两点:
取出来的
Cache并不仅仅是数据,同时还包括这次请求的一些
Header
硬过期 软过期
我们可以看到
Cache中有两个字段来描述缓存过期:
Cache.ttlvs
Cache.softTtl。什么区别呢?如果
ttl过期,那么这个缓存永远不会被使用了;如果
softTtl没有过期,这个数据直接返回;如果
softTtl过期,那么这次请求将有两次返回,第一次返回这个
Cahce,第二次返回网络请求的结果。想想,这个是不是满足我们很多场景呢?先进入页面展示缓存,然后再刷新页面;如果这个缓存太久了,可以等待网络数据回来之后再展示数据,是不是很赞?
NetworkDispatcher
执行网络请求的工作线程,默认有4个线程,它不停地从网络队列中取任务执行。public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Request<?> request; while (true) { long startTimeMs = SystemClock.elapsedRealtime(); // release previous request object to avoid leaking request object when mQueue is drained. request = null; try { request = mQueue.take(); } catch (InterruptedException e) { if (mQuit) { return; } continue; } try { request.addMarker("network-queue-take"); //取消 if (request.isCanceled()) { request.finish("network-discard-cancelled"); continue; } //通过Http栈实现客户端发送网络请求 NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete"); // 如果缓存软过期,那么会重新走网络;如果server返回304,表示上次之后请求结果数据本地并没有过期,所以可以直接用本地的,因为之前Volley已经发过一次Response了,所以这里就不需要再发送Response结果了。 if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); continue; } Response<?> response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); //更新缓存 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); } } }
Request
Request中主要封装了一个请求的各类
Http协议信息,比如
URL,请求方法,请求的优先级,请求重试的策略,缓存策略等。
这里说一下其中比较有意思的重发策略,如果一次请求发生超时异常,比如
SocketTimeoutException ConnectTimeoutException,我们可以为
Request配置一个
RetryPolicy,你可以指定重发这个Request的次数,以及每次失败之后重新设置这个请求的超时时间(第一次失败之后,你可以调整第二次请求的超时时间增加,以减少失败的可能性)。
反序列化
Request最重要的功能就是提供了内容的反序列化,通过不同的子类来实现不同的序列化功能。比如,如果请求结果是一个Json的对象,我们可以使用
JsonObjectRequest,如果是一个普通字符,使用
StringRequest,同时,我们也可以很方便的定制自己的Request,通过复写
Response<T> parseNetworkResponse(NetworkResponse response);方法即可。
默认的
JsonRequest使用
org.json中的
Json解析,我们使用
Gson来进行解析能够构造一个更加通用的处理
json返回的
Request:
public class JsonGRequest<T> extends Request<T> { private static Gson gson = new Gson(); private Response.Listener<T> mListener; public JsonGRequest(String url, Response.ErrorListener listener,Response.Listener responseListener) { super(url, listener); this.mListener = mListener; } public JsonGRequest(int method, String url, Response.ErrorListener listener) { super(method, url, listener); } @Override protected Response<T> parseNetworkResponse(NetworkResponse response) { return Response.success(gson.fromJson(new InputStreamReader(new ByteArrayInputStream(response.data)),getType()), HttpHeaderParser.parseCacheHeaders(response)) } @Override protected void deliverResponse(T response) { if(mListener != null) { mListener.onResponse(response); } } //获取指定的泛型类型 protected Type getType() { Type superclass; for(superclass = this.getClass().getGenericSuperclass(); superclass instanceof Class && !superclass.equals(JsonGRequest.class); superclass = ((Class)superclass).getGenericSuperclass()) { ; } if(superclass instanceof Class) { throw new RuntimeException("Missing type parameter."); } else { ParameterizedType parameterized = (ParameterizedType)superclass; return parameterized.getActualTypeArguments()[0]; } } }
ImageRequest
Volley专门为图片请求提供了
ImageRequest,主要是反序列化了一下数据流到
BitMap,还可以制定图片的大小,质量等参数。
ImageLoader是
Volley提供的一个用来加载图片的工具,它的内部还是使用
ImageRequest来实现的,主要新加的功能是增加了内存缓存,你可以通过配置
ImageCache来设置内存缓存。
小结
Volley整体代码还是比较简单,思路明确,而且提供了不错的可扩展性,而且各个方面考虑得较为全面。下面我们分析一下
Retrofit的源码。
相关文章推荐
- Qt---基于UDP的网络广播程序
- udp与tcp通信代码
- HTTP协议
- HTTP Servlet 重要的几个方法
- 学习TensorFlow,TensorBoard可视化网络结构和参数
- 学习TensorFlow,TensorBoard可视化网络结构和参数
- UNIX网络编程——shutdown函数(I/O复用并发服务器)
- 使用 acl 协程编写高并发网络服务
- WiFi时,显示图片高清图;网络状态为蜂窝移动网络时,显示图片缩略图http://gold.xitu.io/entry/57285c5e2e958a0068db2f9e
- AJAX入门 之 XMLHttpRequest 状态与 onreadystatechange 事件(三)
- 使用WebService进行网络编程【工具类】
- Linux下的C语言开发(网络编程)
- Qt---获取本机网络信息
- 学习笔记——网络基础(一)
- wifi eth gprs 网络自动切换 脚本
- 通过HttpServletRequest获取上一个页面
- HTTP中GET和POST的区别
- tensorflow入门简单卷积神经网络
- 高性能HTTP加速器Varnish安装与配置
- HTTP协议