1、概述
VolleySupport是基于Google的Volley框架,在其基础上简单封装完成的。添加的代码主要位于manager目录下。Volley的源码已经加入到Android Source中,路径为frameworks/volley
2 Volley源码分析
2.1 功能说明
volley提供功能如下:
JSON,图像等的异步下载;
网络请求的排序(scheduling)
网络请求的优先级处理
缓存
多级别取消请求
和Activity和生命周期的联动(Activity结束时同时取消所有网络请求)
2.2 Volley整体设计思路
首先添加实现Request抽象类的任务到RequestQueue中
然后通过Dispatch不断从RequestQueue中取出请求:
优先判断CacheDispatcher中是否有缓存数据
Cache中没有则通过NatworkDispatcher,从网络获取数据并缓存。
最后通过ResponseDelivery分发数据,做回调处理。
如下图所以:
2.3 主要类分析
Volley:Volley 工具类,对外暴露的 API,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue。这里进行了版本号判断,大于等于9就使得HttpStack对象的实例为HurlStack,小于9则实例为HttpClientStack。
Request:表示一个请求的抽象类。StringRequest、JsonRequest、ImageRequest等都是它的子类,表示某种类型的请求。
RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、NetworkDispatcher数组(用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口),通过 start() 函数启动时会启动CacheDispatcher和NetworkDispatchers。
mCacheQueue 缓存请求队列
mNetworkQueue 网络请求队列
mCurrentRequests 正在进行中(正在请求中或者正在分发中),尚未完成的请求集合
mWaitingRequests 等待请求的集合,如果一个请求正在被处理并且可以被缓存,后续的相同 url 的请求,将进入此等待队列CacheDispatcher:一个线程,用于调度处理缓存请求。启动后会不断从缓存请求队列mCacheQueue中取请求处理:
对于已经取消的请求,标记为跳过并结束这个请求;
新的或者过期的请求,直接放入mNetworkQueue中由N个NetworkDispatcher进行处理;
已获得缓存信息(网络应答)却没有过期的请求,由Request的parseNetworkResponse进行解析,从而确定此应答是否成功。如果需要更新缓存那么该请求还会被放入mNetworkQueue中由N个NetworkDispatcher进行处理。
最后将请求和应答交由Delivery(ExecutorDelivery)分发者进行处理,
NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。Volley中是使用了一个NetworkDispatcher线程数组去处理网络请求的。
ResponseDelivery:返回结果分发接口,最终调用Listener和ErrorListener方法通知UI线程。如果等待列表mWaitingRequests中存在相同URL的请求,则会将剩余的层级请求全部丢入mCacheQueue交由CacheDispatcher进行处理。目前实现类只有基于ExecutorDelivery的在入参 handler 对应线程内进行分发。
HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack。
Network:调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse。其实现类为BasicNetwork。
Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCache。volley还提供了一个NoCache,也就是无缓存。
NetworkDispatcher得到请求结果后判断是否需要存储在 Cache。
CacheDispatcher会从 Cache 中取缓存结果。
类图如下:
2.4 添加请求
添加请求过程add分析如下
2.5 缓存处理
volley处理请求流程:
2.6 网络请求
2.7 Response转换
2.8 数据分发处理
3对volley的封装
主要位于manager
包中。
3.1 RequestManager
简单封装了初始化和获取RequesQueue的方法。
public static void init(Context context, String userAgent) {
RequestManager.init(context, userAgent, 4);
}
public static void init(Context context, String userAgent, int threadPool) {
mRequestQueue = Volley.newRequestQueue(context, userAgent, threadPool);
}
3.2 Multipart请求支持
流格式上传文件支持包含三个文件
1、MultipartRequestParams为参数列表,支持两种类型参数
普通键值对
文件或文件流
MultipartRequestParams内部其实是一个FileWrapper,用来封装需要上传的流(文件最终也会被转换成流)。
private static class FileWrappers {
public InputStream[] inputStreams;
public String[] fileNames;
public String contentType;
public FileWrappers(InputStream[] inputStreams, String[] fileNames,
String contentType) {
this.inputStreams = inputStreams;
this.fileNames = fileNames;
this.contentType = contentType;
}
public String getFileName(int i) {
if (fileNames != null) {
return fileNames[i];
} else {
return "nofilename";
}
}
}
MultipartRequestParams提供了两类put方法,一种是类似map的可以存储键值对,这里略过;另一种可以put进来流或者文件,如下。
public void put(String key, File[] files) {
try {
FileInputStream[] inputStreams = new FileInputStream[files.length];
String[] fileNames = new String[files.length];
for (int i = 0; i < files.length; i++) {
if (files[i] != null) {
inputStreams[i] = new FileInputStream(files[i]);
fileNames[i] = files[i].getName();
} else {
inputStreams[i] = null;
fileNames[i] = null;
}
}
put(key, inputStreams, fileNames);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public void put(String key, InputStream[] streams, String[] fileNames) {
put(key, streams, fileNames, null);
}
public void put(String key, InputStream[] streams, String fileNames[], String contentType) {
if (key != null && streams != null) {
fileParams.put(key, new FileWrappers(streams, fileNames, contentType));
}
}
最终文件保存在了fileParams中。
MultipartRequestParams还提供了一个方法getEntity,用来将前面添加进来的参数和流封装成HttpEntity。
public HttpEntity getEntity() {
HttpEntity entity = null;
MultipartEntity multipartEntity = new MultipartEntity();
// Add string params
for (ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {
multipartEntity.addPart(entry.getKey(), entry.getValue());
}
if (!fileParams.isEmpty()) {
// Add file params
int currentIndex = 0;
int lastIndex = fileParams.entrySet().size() - 1;
for (ConcurrentHashMap.Entry<String, FileWrappers> entry : fileParams.entrySet()) {
FileWrappers file = entry.getValue();
if (file.inputStreams != null) {
boolean last = currentIndex == lastIndex;
for (int i = 0; i < file.inputStreams.length; i++) {
boolean isLast;
if (i == file.inputStreams.length - 1) {
isLast = last & true;
} else {
isLast = last & false;
}
if (file.contentType != null) {
multipartEntity.addPart(entry.getKey(), file.getFileName(i), file.inputStreams[i], file.contentType, isLast);
} else {
multipartEntity.addPart(entry.getKey(), file.getFileName(i), file.inputStreams[i], isLast);
}
}
}
currentIndex++;
}
}
entity = multipartEntity;
return entity;
}
2.这里新建了一个MultipartEntity,提供了addPart方法添加流以及普通键值对,最终通过out写入流中。
public void addPart(final String key, final String fileName, final InputStream fin, String type, final boolean isLast) {
writeFirstBoundaryIfNeeds();
try {
type = "Content-Type: " + type + "\r\n";
out.write(("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fileName + "\"\r\n").getBytes());
out.write(type.getBytes());
out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes());
final byte[] tmp = new byte[4096];
int l = 0;
if (fin != null) {
while ((l = fin.read(tmp)) != -1) {
out.write(tmp, 0, l);
}
}
if (!isLast) {
out.write(("\r\n--" + boundary + "\r\n").getBytes());
} else {
writeLastBoundaryIfNeeds();
}
out.flush();
} catch (final IOException e) {
e.printStackTrace();
} finally {
try {
if (fin != null) {
fin.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
}
3.最终实现了一个MultipartRequest,这里继承的是StringRequest,因为差别不大。重写了getBody()
方法,把MultipartEntity中的Entry读取出来,通过getBody返回一个字节数组给Http。(默认的getbody是获取Post的参数数据返回字节数组)
public byte[] getBody() throws AuthFailureError {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if(params != null) {
httpEntity = params.getEntity();
try {
httpEntity.writeTo(baos);
} catch (IOException e) {
e.printStackTrace();
}
}
return baos.toByteArray();
}
4 取消请求
RequestQueue中添加了一个取消请求的方法,可以在不需要这个网络请求的时候通过tag取消它。
public void cancelAll(final Object tag) {
if (tag == null) {
throw new IllegalArgumentException("Cannot cancelAll with a null tag");
}
cancelAll(new RequestFilter() {
@Override
public boolean apply(Request<?> request) {
return request.getTag() == tag;
}
});
}
5 重试机制
volley提供了重试机制接口RetryPolicy
,且提供了一个默认的实现类DefaultRetryPolicy
public class DefaultRetryPolicy implements RetryPolicy {
private int mCurrentTimeoutMs; //当前超时时间毫秒值
private int mCurrentRetryCount; //当前重试次数
private final int mMaxNumRetries; //最大重试次数
private final float mBackoffMultiplier;//超时重试延时
public static final int DEFAULT_TIMEOUT_MS = 15*1000;
public static final int DEFAULT_MAX_RETRIES = 2;
public static final float DEFAULT_BACKOFF_MULT = 1f;
public DefaultRetryPolicy() {//使用默认的构造方法参数实例化
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
@Override
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++;
//超时次数*重试延时 结果为下次重试网络请求时间,支持定制曲线增加时间。
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (!hasAttemptRemaining()) {
throw error;
}
}
protected boolean hasAttemptRemaining() {
return mCurrentRetryCount <= mMaxNumRetries;
}
}
6 地址
maven地址 com.android.common:VolleySupport:1.0.1
源码地址 maven
目录下