Android网络框架源码分析一---Volley
公司最近新起了一个项目,对喜欢尝鲜的我们来说,好处就是我们可以在真实的项目中想尝试一些新技术,验证想法。新项目对网络框架的选取,我们存在三种方案:
1.和我们之前的项目一样,使用Loader + HttpClient + GreenDao + Gson + Fragment,优点是可定制性强,由于使用Google家自己的Loader和LoaderManager,代码健壮性强。
缺点是整套代码学习成本较高,使用过程中样板代码较多,(比如每一个Request都需要产生一个新类)
2.Volley,作为Google在IO大会上得瑟过的一个网络库,其实不算什么新东西(2013 IO发布),使用较为简单,请求可以取消,可以提供优先级请求,看起来还是不错的。
3.Retrofit,一款为了使请求极度简单化的REST API Client,呼声也很高,使用门槛几乎是小白型。
如何选择呢?首先干掉1,因为对新人的学习成本确实太高,如果要快速开发一个项目,高学习成本是致命的,同时使用起来样板代码很多。
那么如何在Volley和Retrofit中选择呢?尽管网上有很多文章在介绍两个框架的使用方法,而对于其原理,特别是对比分析较少,如果你手里有一个项目,如何选择网络模块呢?
这里将分两篇文章从源码的角度对比分析这两个开源框架,希望能对你有所帮助。这是上篇,下篇Retrofit在这里,其中有干货总结哦~。
需要注意的是,这两篇文章并不会是一个入门使用的帮助文档,建议你在看本文的时候,最好看过官方文档和DEMO。
首先说明一下这两个网络框架在项目中的层次:
从上图可知,不管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会被优先调度执行。NetworkResponseHttp响应的封装,其中包括返回的状态码 头部 数据等。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)来往任务队列中增加请求:
一个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.ttlvsCache.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的源码。
喜欢的用户
SherVega2016.01.29 16:58
像疯子一样活着_文2016.01.23 23:59Blue32092016.01.23 19:34
dd9597c216062016.01.22 00:58
背靠背2016.01.21 18:49
Euterpe2016.01.20 22:47theScoreONE2016.01.20 00:01
NeverMore_Love2016.01.15 22:29
131947fac2f62016.01.14 10:34皓矾星2016.01.14 01:12
Roar咆哮2016.01.12 23:07
手写的永远2016.01.12 08:110015cbf943eb2016.01.11 23:50
蔡印2016.01.11 10:50
004662fd036f2016.01.11 10:13
Android网络框架源码分析一---Volley的更多相关文章
- Android开源框架源码分析:Okhttp
一 请求与响应流程 1.1 请求的封装 1.2 请求的发送 1.3 请求的调度 二 拦截器 2.1 RetryAndFollowUpInterceptor 2.2 BridgeInterceptor ...
- # Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析#
Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析 Volley源码一共40多个类和接口.除去一些工具类的实现,核心代码只有20多个类.所以相对来说分析起来没有那么吃力.但是要想分析透 ...
- Android Small插件化框架源码分析
Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ...
- Spark RPC框架源码分析(一)简述
Spark RPC系列: Spark RPC框架源码分析(一)运行时序 Spark RPC框架源码分析(二)运行时序 Spark RPC框架源码分析(三)运行时序 一. Spark rpc框架概述 S ...
- Spark RPC框架源码分析(二)RPC运行时序
前情提要: Spark RPC框架源码分析(一)简述 一. Spark RPC概述 上一篇我们已经说明了Spark RPC框架的一个简单例子,Spark RPC相关的两个编程模型,Actor模型和Re ...
- iOS常用框架源码分析
SDWebImage NSCache 类似可变字典,线程安全,使用可变字典自定义实现缓存时需要考虑加锁和释放锁 在内存不足时NSCache会自动释放存储的对象,不需要手动干预 NSCache的key不 ...
- 【Redis】事件驱动框架源码分析
aeEventLoop初始化 在server.c文件的initServer函数中,对aeEventLoop进行了初始化: 调用aeCreateEventLoop函数创建aeEventLoop结构体,对 ...
- 【Redis】事件驱动框架源码分析(单线程)
aeEventLoop初始化 在server.c文件的initServer函数中,对aeEventLoop进行了初始化: 调用aeCreateEventLoop函数创建aeEventLoop结构体,对 ...
- 介绍开源的.net通信框架NetworkComms框架 源码分析
原文网址: http://www.cnblogs.com/csdev Networkcomms 是一款C# 语言编写的TCP/UDP通信框架 作者是英国人 以前是收费的 售价249英镑 我曾经花了 ...
随机推荐
- WebApp 设计要素
从去年开始就负责公司WebApp的产品跟设计工作,最近整体大改了两个版本,也算累积了一些实际的经验.在不断学习的过程中,发现对于WebApp可以直接用于项目上的资料比较零碎,在这里总结一下,供初做 W ...
- 安装时遇到:正在尝试其它镜像。 http://mirrors.btte.net/centos/7.2.1511/extras/x86_64/repodata/repomd.xml: [Errno 14] curl#6 - "Could not resolve host: mirrors.btte.net; 未知的错误"
我出现这种错误是因为网络链接问题,因为我设置虚拟机网络链接为VmNET8,设置了nat模式,使得我本地机可以访问虚拟机的linux服务器.但是打开虚拟机的浏览器却不能上网了.所以现在我用xshell装 ...
- sql 数据库 初级 个人学习总结(一)
数据库个人总结(初级)1.增删改查 insert into 表名 values ('条件','条件2') delete from 表名 where 条件 update 表名 set=条件值 where ...
- IOS第16天(4,Quartz2D柱状图)
*** #import "HMBarView.h" #import "UIColor+Random.h" @implementation HMBarView - ...
- Windows2003中IIS的安全设置技巧
在Windows Server 2003中对于IIS的安全设置具有十分重要的意义,所以掌握IIS安全设置的六大技巧是一个网管员必备的基本技能.下面就是对IIS的安全设置的六大技巧. 技巧1.安装系统补 ...
- web.xml配置error-page
一. 通过错误码来配置error-page <error-page> <error-code>404</error-code> <location>/e ...
- gets(),fgets()的作用机制探究
gets(),fgets() scanf("%d",&a)若接受形如 2 这样的输入后,缓冲区内会留一个\n,此后若调用gets等函数时会读出这个换行出现错误,需注意 fg ...
- QThread
#ifndef MYTHREAD_H #define MYTHREAD_H #include<QThread> class MyThread : public QThread { Q_OB ...
- windows下MySQL更改数据库文件目录及1045,1067错误
MySQL安装时不能选择数据库文件的安装位置, 也没有可用的直接更改数据库目录的工具,要想更改数据目录,方法如下: MySQL安装并配置完毕,默认的数据哭安装目录为 C:/ProgramData/My ...
- 使用复合索引代替单键索引,来避免单键有null值的情况
查看原表: SQL> select count(*) from t1; COUNT(*) ---------- 3229088 SQL> select count(*) from t1 w ...
