1、概述
在开发的时候多数情况下我们都会使用HTTP协议和服务器进行通信。Android SDK 提供了两种方式进行HTTP通信,即HttpClient和HttpURLConnection。不过直接使用HttpClient和HttpURLConnection进行通信需要自己处理通信的细节,包括数据缓存、超时处理等细节。于是一些Android网络通信框架就应运而生,而Volley就是在2013 Google I/O大会上发布的Android平台的网络通信库,能使网络通信更快、更精简、更健壮。Google推荐我们使用Volley库。所以有必要深入学习Volley库的内部实现。
2、介绍
2.1 Volley的功能
Volley提供了网络通讯功能,包括JSON、图片、String类型数据的请求和解析,并且Volley的可扩展性非常强,开发者可根据自己的项目需要进行自定义扩展(具体哪些地方可以扩展后面会做分析)。
简单来说,它提供了如下功能:
* JSON,图片等多种数据类型的异步请求
* 网络请求的排序
* 网络请求的优先级处理
* 数据的缓存
2.2 Volley的特点
优点
* 包体小 Volley库大小才120k
* 适合进行数据量不大,但请求频繁的应用场景
* 面向接口编程,容易扩展
* 缓存机制中数据只缓存到文件中,而没有在内存中,这样可节省运行内存
* 使用标准的HTTP缓存机制,保证缓存的数据是和服务器一致
缺点
* 使用的是HttpURLConnection和HttpClient,但Android6.0不支持HttpClient
* 对数据量大的文件下载,用户体验糟糕(后面会分析原因)
* 只磁盘缓存机制,而没有在内存缓存机制
2.3 Volley的使用
Volley的用法非常简单,只需要几行代码即可完成数据请求:
首先需要获取一个RequestQueue对象:
RequestQueue mRequestQueue = Volley.newRequestQueue(Context);
接着创建相应的请求对象Request,并添加到RequestQueue队列即可。
下面看一下各种Request的使用
StringRequest
StringRequest是请求内容为字符串的请求对象,StingRequest的创建方式如下:
String url = "http:\\www.baidu.com";
StringRequest request = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String s) {
// 请求成功回调
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
// 请求失败回调
}
}
);
参数1:发起HTTP请求的方法
参数2:发起请求的服务器地址
参数3:请求成功回调接口
参数4:请求失败回调监听
JsonRequest
JsonRequest请求对象是用于请求Json格式的数据,请求成功后会返回一个JSONObject对象,JsonRequest创建方式和StringRequest类似
String url = "http:\\www.baidu.com";
JsonObjectRequest jsonRequest = new JsonObjectRequest(Request.Method.GET, url, null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject jsonObj) {
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
}
}
);
参数1:发起HTTP请求的方法
参数2:发起HTTP请求的服务器地址
参数3:如果参数1是一个POST方法,那么这个参数就是要提交的JSONObject对象,该参数可为null
参数4:请求成功回调接口
参数5:请求失败回调接口
ImageRequest
ImageRequest请求对象是用于请求Bitmap数据,请求成功后会返回一个Bitmap对象,ImageRequest创建方式和参数说明如下:
String url = "http:\\www.baidu.com";
ImageRequest imageRequest = new ImageRequest(url,
new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap jsonObj) {
}
}, 0, 0, Bitmap.Config.ARGB_8888,
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
}
}
);
参数1:发起HTTP请求的服务器地址
参数2:请求成功回调接口
参数3:请求的图片的最大长度,该参数是根据你要展示该图片的ImageView的最大长度来设定,可设置为0
参数4:请求的图片的最大高度,该参数是根据你要展示该图片的ImageView的最大高度来设定,可设置为0
参数5:请求的图片的每个像素点的需要占用的字节数,占用的字节越多,图片越清晰
参数6:请求失败回调接口
注意:合理的设置参数3、参数4、参数5可减小返回的Bitmap对象所占用的内存。
上面介绍Volley库内置 的三种请求类型StringRequest、JSONObjectRequest、ImageRequest的创建。前面说了,Volley库是一个面向接口编程的网络库,所以可很容易进行扩展
所以我们可自定义自己的请求对象,只需继承Request对象,并实现数据的解析方法。
自定义Request
这里自定义一个文件请求类型,用于从服务器下载一个文件数据。
public class FileRequest extends Request<File> {
private final Response.Listener<File> mListener;
public FileRequest(String url, String savePath, Response.Listener<File> listener, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
mListener = listener;
}
@Override
public Priority getPriority() {
return Priority.LOW;
}
@Override
protected Response<File> parseNetworkResponse(NetworkResponse networkResponse) {
// 对原始数据networkResponse.data进行处理
}
@Override
protected void deliverResponse(File file) {
this.mListener.onResponse(file);
}
}
看一下请求对象都支持哪些操作:
* setShouldCache(boolean shouldCache) 设置该请求对象如果缓存中数据,是否使用缓存的数据,以及从服务器请求回来的最新数据是否需要缓存到本地
* cancel( ) 取消该请求
3、源码解析
现在已知道Volley的基本用法,接下来我们从源码层面来解析Volley的内部实现机制。在使用Volley时需要先创建一个RequestQueue队列
那我们先从RequestQueue的创建开始。
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
// 磁盘缓存的路径
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
// 这里需要注意,当参数stack为null时,如果系统版本为9及以上,创建HurlStack对象,9以下则创建HttpClientStack
// 下面会解释HurlStack和HttpClientstack的作用
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
// 使用上面的stack创建一个Network,后面会分析Network
Network network = new BasicNetwork(stack);
//创建一个请求队列,并启动。创建请求队列时需要传递两个对象:磁盘缓存对象和Network对象
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
先来介绍RequestQueue这个类
RequestQueue的创建
public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
// 存放网络分发线程的数组
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
参数1:磁盘缓存,用于缓存HTTP请求返回的数据
参数2:用于发起HTTP请求
参数3:HTTP请求线程数量,如果用户不设定该参数,默认开启4个线程,进行HTTP请求
参数4:因为HTTP请求是在子线程中操作的,得到的结果也是在子线中,我们需要通过ResponseDelivery把结果从子线程传递到主线程。
RequestQueue内部重要的数据集合:
/**
*
* 如果由多个Request请求同一张相同参数的图片,那么Volley只处理一个请求,其它的请求存储在这个集合中等待处理结果。
*/
private final Map<String, Queue<Request<?>>> mWaitingRequests =
new HashMap<String, Queue<Request<?>>>();
/**
* 存储全部的请求
*/
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
/** 缓存队列,缓存线程就是从这个队列中取数据 */
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
/** 网络请求队列,网络分发线程从这个队列中取数据 */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
RequestQueue提供两个比较重要的接口RequestQueue.start()和RequestQueue.add(request)
RequestQueue.start()启动网络分发线程NetworkDispatcher和缓存线程CacheDispatcher,这两种类型的线程从RequestQueue不断的取出请求对象进行处理 。
public void start() {
// 首先停掉全部线程
stop();
// 接着创建一个缓存分发线程,并启动线程,该线程用于处理缓存操作
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// 创建网络分发线程用于HTTP请求,注意这里把请求队列传递到线程。
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
注意:上面启动一个缓存线程、网络请求线程的数量默认是4个。
RequestQueue.add(request)接口用于向RequestQueue添加请求
public <T> Request<T> add(Request<T> request) {
request.setRequestQueue(this);
// 1:每个请求都添加到这个集合中
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
...
//2:如果对于这个请求,不允许从缓存中拿数据,则把该请求添加到网络请求队列中,并返回
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
// 3:判断是否有相同的请求
if (mWaitingRequests.containsKey(cacheKey)) {
// 4:有,则添加到等待集合
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
} else {
// 5:没有,则添加到缓存处理队列,并在等待集合中记录
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
下面流程图是RequestQueue.add(request)的执行流程。
下面分析NetworkDispatcher这个类,这个类的实现很简单。
NetworkDispatcher继承Thread实现一个子线程
public class NetworkDispatcher extends Thread {
/** 该集合就是网络请求队列 */
private final BlockingQueue<Request<?>> mQueue;
...
}
重点看一下NetworkDispatcher.run()
public class NetworkDispatcher extends Thread {
...
@Override
public void run() {
// 注意这里是死循环
while (true) {
Request<?> request;
try {
// 1:从网络请求队列中取出请求信息.
request = mQueue.take();
} catch (InterruptedException e) {
if (mQuit) {
return;
}
continue;
}
try {
//2:判断请求是否被取消
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
// 3:通过Network对象去执行一个请求,并得到请求结果(Network后面再分析).
NetworkResponse networkResponse = mNetwork.performRequest(request);
// 4:对请求到的原始数据解析为我们想要的数据
Response<?> response = request.parseNetworkResponse(networkResponse);
//5:如果允许,则把原始数据保存到缓存
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
}
// 6:最后把请求结果返回到主线程
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
mDelivery.postError(request, volleyError);
}
}
}
...
}
下面分析CacheDispatcher这个类
该类也其实和NetworkDispatcher都是实现数据的请求,区别是NetworkDispatcher是请求网络数据,而CacheDispatcher是请求缓存数据
我们直接来看CacheDispatcher的实现,代码中都有注释
public class CacheDispatcher extends Thread {
...
@Override
public void run() {
// 注意这里是个死循环
while (true) {
try {
// 从缓存队列中取出缓存请求
final Request<?> request = mCacheQueue.take();
// 判断请求是否取消
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 取出缓存,如果不存在缓存,那么就添加到网络请求队列中
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
mNetworkQueue.put(request);
continue;
}
// 如果有缓存,但缓存已过期,则添加到网络请求队列中
if (entry.isExpired()) {
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// 把原始数据解析为先要的数据类型
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
// 判断数据是否需要更新
if (!entry.refreshNeeded()) {
// 若不需要,直接把数据返回给用户
mDelivery.postResponse(request, response);
} else {
// 若需要,则把数据返回给用户后,把发送一个网络请求更新数据
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) {
if (mQuit) {
return;
}
continue;
}
}
}
}
上面分析的NetworkDispatcher内部是使用Network.performRequest( )进行网络请求,接下来我们介绍Network
Network是一个接口,提供了一个请求数据的接口。唯一的实现类是BasicStack,类的关系图如下:
BasicNetwork实现了performRequest( ),BasicNetwork有一个HttpStack的引用,这个引用可指向HttpClientStack,也可指向HurlStack,这两个类后面再分析。我们先来看一下BasicNetwork.performRequest( )的实现。
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = Collections.emptyMap();
try {
// 1:设置请求头
Map<String, String> headers = new HashMap<String, String>();
addCacheHeaders(headers, request.getCacheEntry());
// 2:通过mHttpStack请求数据
httpResponse = mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
// 3:根据请求结果状态码进行后续处理,如:请求超时处理、 重定向处理、
int statusCode = statusLine.getStatusCode();
...
return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
SystemClock.elapsedRealtime() - requestStart);
} catch (Exception e) {
// 错误处理
...
}
}
}
注意第二步会通过mHttpStack去进行一个请求,HttpStack有两个实现类,这两个实现类分别使用了不同的网络请求方式。
HttpClientStack使用Apache提供的HttpClient实现网络请求
HurlStack使用Java提供的HttpURLConnection实现网络请求
注意:移除了对支持Apache HTTP的支持,可改用 HttpURLConnection 类,该类效率更高。 要继续使用 Apache HTTP API,必须自行添加依赖
如果没指定Http的请求方式,那Volley内部会根据系统的版本号创建HttpStack。
这里就不分析HttpStack的两个实现了网络请求细节。
返回到NetworkDispatcher.run()的第四步,这一步是调用Request.parseNetworkResponse()对请求到的二进制数据进行解析,上面已经讲解Request的几个子类,每个子类对parseNetworkResponse( )的实现都是不一样的,即对二进制的解析方式是不一样的。
NetworkDispatcher.run()的第6步把解析的结果返回到主线程中。
4、总结
下面给出一个Volley框架的类图