您的位置:首页 > 理论基础 > 计算机网络

Android网络框架源码分析一----Volley

2016-07-06 23:39 489 查看


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。

首先说明一下这两个网络框架在项目中的层次:



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.ttl
 vs 
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
,主要是反序列化了一下数据流到
BitMa
p,还可以制定图片的大小,质量等参数。

ImageLoader
Volley
提供的一个用来加载图片的工具,它的内部还是使用
ImageRequest
来实现的,主要新加的功能是增加了内存缓存,你可以通过配置
ImageCache
来设置内存缓存。


小结

Volley
整体代码还是比较简单,思路明确,而且提供了不错的可扩展性,而且各个方面考虑得较为全面。下面我们分析一下
Retrofit
的源码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: