2017-06-17
Volley is an HTTP library that makes networking for Android apps easier and, most importantly, faster.
源码地址: https://github.com/google/volley
1. 如何使用
已经有很多文章介绍了Volley,不再重复。下面是一些参考:
2. 设计思路
在volley中有三种类型的线程:
- 主程/其他线程:主线程即UI线程。
主程/其他线程
负责将网络请求交给RequestQueue。 - 网络线程:发起网络调用,得到返回数据。默认是4个线程。
- 缓存线程:如果请求允许使用缓存,则缓存线程尝试去取缓存数据。
那么网络线程、缓存线程是从什么地方拿到的请求呢?答案是阻塞式队列。
涉及到两个队列存储网络请求:
- 网络线程从 PriorityBlockingQueue 类型的队列中取网络请求,称之为
mNetworkQueue
。 - 缓存线程也是 PriorityBlockingQueue 类型的队列中取网络请求,称之为
mCacheQueue
。
而RequestQueue 本身不是队列,不过其内部主要逻辑是将请求对象放入mNetworkQueue
或mCacheQueue
。
这里有一个问题,两个相同的支持缓存的请求(请求1、请求2)几乎同时到来,那么其实没必要对请求2进行处理,只需要等请求1拿到响应数据后,把数据顺便给请求2即可。Volley中使用 Map<String, Queue<Request<?>>>
记录有没有支持缓存的请求正在进行,称之为mWaitingRequests
。其中key是请求的key(默认是url),value是请求对象的容器。
2.1 网络请求发起流程
请求发起流程:
- 主线程(其他线程也行)生成请求对象,对象内部包含了请求地址、请求方法、处理返回数据的回调对象等。
- 主线程将请求对象交给RequestQueue对象。以下是RequestQueue对象内部的处理逻辑:
- 判断该请求对象是否允许使用缓存,若不允许,则将请求放入
mNetworkQueue
。流程结束。若允许缓存,进入下一步。 - 得到请求对象的key值(默认是网址Url),根据mWaitingRequests判断当前是否有相同key的网络请求正在进行。如果有,则将该请求对象先存到mWaitingRequests中key对应的容器中,等待已有的相同key的网络请求响应回来后,顺便把结果交给该请求。如果没有,则进入下一步:
- 将请求对象放入mCacheQueue,并记录当前有key为×××的请求正在进行处理。
- 判断该请求对象是否允许使用缓存,若不允许,则将请求放入
2.2 缓存线程
缓存线程是一直运行,内部就是死循环,每一次循环从mCacheQueue拿出一个网络请求进行处理,处理流程如下:
- 根据网络请求的key,判断缓存中是否有已有内容,若没有内容、或者内容过期,则将网络请求扔到
mNetworkQueue
,让网络线程去处理。结束流程。 - 若有未过期的缓存,则将缓存内容作为响应内容。在主线程中执行网络请求中回调对象中的函数。
2.3 网络线程
类似的,网络线程是一直运行,内部就是死循环,每一次循环从mNetworkQueue拿出一个网络请求进行处理,处理流程如下:
- BasicNetwork对网络请求进行处理,得到响应数据。
- 如果该网络请求允许缓存且HTTP响应头也给出了允许缓存相关的信息,则缓存响应结果。
- 在主线程中执行该网络请求中回调对象中的函数。
2.4 缓存设计
缓存使用key-value的形式存取,key是从Request类中拿出来的,默认是网址。StringRequest
、JsonArrayRequest
等网络请求类集成自Request
。可以根据需要在自定义的Request子类中覆盖该方法。
public abstract class Request<T> implements Comparable<Request<T>> {
// ...
public String getCacheKey() {
return getUrl();
}
// ...
}
而value是包含了多个属性,有响应头、响应体、ETag内容、Last-Modified内容、Expires内容等。
DiskBasedCache
是默认的缓存实现,一个基于文件存储的cache,所有文件存在一个目录里。有以下几个有趣的设计:
文件名并不是key。key是存在文件里面的。而文件名是基于key生成的具有(伪)1对1关系的名称。不是md5哈。真的冲突了也没关系,key存在文件里呢。
初始化时,会将本地已有的缓存全部载入内存。但是内存中不会有响应体,如图片、html等。添加缓存时,既存入本地,也加入内存中。删除某条缓存,既删本地,也删内存中对应内容。根据key获取缓存时,先看内存中有没有,没有就直接返回null,有的话再从本地存储中把响应体拿出来。
针对响应体,默认允许本地存储最大为5MB。当添加缓存时,会判断有没有超出限制,若超出,则先删除之前已有的部分缓存,直到满足下面的条件才添加新缓存:
本地缓存占用空间+新缓存要占用的空间 < 最大缓存大小*0.9
2.5 超时和重试
超时时间和重试次数的代码在Request类中已经实现:
public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
mRetryPolicy = retryPolicy;
return this;
}
public final int getTimeoutMs() {
return mRetryPolicy.getCurrentTimeout(); // 单位是毫秒
}
public RetryPolicy getRetryPolicy() {
return mRetryPolicy;
}
直接在Request子类中实现getTimeoutMs
方法是不可能了,所以设置重试策略即可。
重试策略是一个接口,内容如下:
public interface RetryPolicy {
int getCurrentTimeout();
int getCurrentRetryCount();
void retry(VolleyError error) throws VolleyError;
}
结束重试的方式是在retry
方法在合适的时候抛出一个异常即可。
DefaultRetryPolicy
是RetryPolicy
的默认实现,可以指定超时时间和重试次数,以及重试时超时时间如何变化。
2.6 图片自动处理
一张很大的图片直接在view中显示会耗费大量内存,甚至产生OOM(OutOfMemory)问题。一个有效的应对方法是imageview多大,就把图片先处理成相应大小,再在UI上展示。
volley提供了ImageRequest、ImageLoader、NetworkImageView类来处理图片,均使用了IMageRequest
中doParse
方法处理网络请求返回的图片,具体代码在这里。
3. 代码防御
根据Android 网络通信框架Volley简介(Google IO 2013)的建议,在Activity onStop 时,cancelAll 所有请求,这样网络请求如果未请求,则不会发起请求,如果已经请求,返回数据后不会执行网络请求的回调。示例:
@Override pubic void onStop() {
mRequestQueue.cancelAll(this);
//...
}
4. 一些问题的解决方案
4.1 POST 不支持相同参数
POST 方法如果要带数据,需要覆盖getParams
方法。下面是来自 Android Volley完全解析(一),初识Volley的基本用法的一段代码:
StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> map = new HashMap<String, String>();
map.put("params1", "value1");
map.put("params2", "value2");
return map;
}
};
数据的组装其实是在getBody
方法里:
public byte[] getBody() throws AuthFailureError {
Map<String, String> params = getParams();
if (params != null && params.size() > 0) {
return encodeParameters(params, getParamsEncoding());
}
return null;
}
然而,这种实现不支持类似a=1&a=1&b=123
参数名多次出现的场景。
解决办法是,生成Request对象时覆盖getBody
方法。
4.2 设置请求头
生成Request对象时,覆盖getHeaders
方法即可。
public Map<String, String> getHeaders() throws AuthFailureError {
return Collections.emptyMap();
}
4.3 图片上传问题
Volley没有图片上传的实用类/函数,解决办法有两个:
- 将图片转换为base64,以字符串的形式将图片POST到服务器端。
- 基于HTTP上传图片原理,在
getBody
里生成数据。可以参考这里:Android volley 解析(三)之文件上传篇