源码分析
1、入口Volley.newRequestQueue(context),代码如下:
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
这个方法仅仅只有一行代码,只是调用了newRequestQueue()的方法重载,并给第二个参数传入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) {}
//判断HttpStack,默认是null
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable. 在android2.2之前的版本HttpUrlConnection是不可靠的
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
<1> 这个方法里先判断如果stack是等于null的,则去创建一个HttpStack对象,这里会判断如果手机系统版本号是大于9的,则创建一个HurlStack的实例,否则就创建一个HttpClientStack的实例。
HurlStack的内部就是使用HttpURLConnection进行网络通讯的。
而HttpClientStack的内部则是使用HttpClient进行网络通讯的。
stack主要是用于执行真正的网络请求。
<2> 创建好了HttpStack之后,接下来又创建了一个Network对象,BasicNetwork中new了一个ByteArrayPool字节缓存池。
public BasicNetwork(HttpStack httpStack) {
this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
}
public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
mHttpStack = httpStack;
mPool = pool;
}
构造这个字节缓存池主要是因为当网络请求得到返回数据,需要在内存开辟出一块区域来存放得到的网络数据,然后拿到解析在UI显示。当客户端频繁的数据请求是,就会每次都要去开辟出一块区域,等不用的时候,GC进行回收,如此GC的负担就相当的重,频繁GC对客户端的性能有直接影响。ByteArrayPool主要就是当需要使内存区域的时候,是先查找缓冲池中有无适合的内存区域,如果没有合适的区域就创建,如果有,直接拿来用,不必每次存数据都要进行内存分配,从而减少内存分配的次数。这样,就会大大减少内存区域堆内存的波动和减少GC的回收。从而提高性能以及处理能力。
<3> new一个RequestQueue对象,并调用它的start()方法进行启动。最后将RequestQueue返回,这样newRequestQueue()的方法就执行结束了。
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
blic RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));//ExecutorDelivery用于处理响应
}
public RequestQueue(Cache cache, Network network, int threadPoolSize,ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];//网络线程调度器,默认开启4个线程
mDelivery = delivery;
}
ExecutorDelivery继承ResponseDelivery,内部维护了一个Executor,在使用ExecutorDelivery发送响应消息的时候会调用Executor的execute方法
pblic ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
//使用handler将响应消息切换回UI线程
handler.post(command);
}
};
}
<4> HttpStack的设计用到的就是策略模式,通过策略模式,在SDK不同的版本时选用不同的策略
2、RequestQueue.start()方法
public void start() {
this.stop();
//创建CacheDispatcher缓存调度器实例,然后调用了它的start开启运行
this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
this.mCacheDispatcher.start();
//创建networkDispatcher网络调度器实例,然后调用了它的start开启运行
for(int i = 0; i < this.mDispatchers.length; ++i) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
this.mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
<1> 首先调用stop()方法退出正在运行的所有CacheDispatcher和NetworkDispatcher任务分发器
public void stop() {
//关闭缓存调度器
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
//关闭网络调度器
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
<2> CacheDispatcher继承Thread,是一个线程,调用start方法就会启用run方法。
<3> NetworkDispatcher继承自Thread的,在RequestQueue的构造方法中知道默认是创建4个网络线程。并分别调用它们的start()方法。就意味着可以并发进行4个请求,大于4个,会排在队列中。这就说明volley适合数据量小的请求
由上面知道当调用了Volley.newRequestQueue(context)之后,默认会有五个线程一直在后台运行,其中1个CacheDispatcher是缓存线程,4个NetworkDispatcher是网络请求线程。
3、创建request请求
经过上面两步后,就创建好了RequestQueue,之后就需要构建出相应的Request,比如StringRequest。
public class StringRequest extends Request<String> {
private final Listener<String> mListener;
public StringRequest(int method, String url, Listener<String> listener,
ErrorListener errorListener) {
//调用父类
super(method, url, errorListener);
mListener = listener;
}
/**
* Creates a new GET request.
*
* @param url URL to fetch the string at
* @param listener Listener to receive the String response
* @param errorListener Error listener, or null to ignore errors
*/
public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
//监听response
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
//解析response
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
}
<1> 除了StringRequest之外,volley还提供了ImageRequest、JsonArrayRequest、JsonObjectRequest都继承Request
唯一的区别就是对返回数据的解析方式(parseNetworkResponse)不同,使用的是模板方法模式,对解析方式进行抽象,让子类分别实现。
<2> 在构造方法里面调用的父类的构造方法。进入父类的构造方法:
public Request(int method, String url, Response.ErrorListener listener) {
mMethod = method;
mUrl = url;
mErrorListener = listener;
setRetryPolicy(new DefaultRetryPolicy());
mDefaultTrafficStatsTag = TextUtils.isEmpty(url) ? 0: Uri.parse(url).getHost().hashCode();
}
调用setRetryPolicy方法设置一个默认的DefaultRetryPolicy重试策略,
public DefaultRetryPolicy() {
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
DEFAULT_TIMEOUT_MS:连接超时时间,默认是2500
DEFAULT_MAX_RETRIES:最大重试次数,默认是1
DefaultRetryPolicy实现了RetryPolicy接口,是策略模式,是通过调用retry方法来实现重试机制
3、RequestQueue.add()方法
然后调用RequestQueue的add()方法将Request传入就可以完成网络请求操作了。
public Request add(Request request) {
request.setRequestQueue(this);
Set var2 = this.mCurrentRequests;
//将请求添加到mCurrentRequests队列中
synchronized(this.mCurrentRequests) {
this.mCurrentRequests.add(request);
}
request.setSequence(this.getSequenceNumber());
request.addMarker("add-to-queue");
//判断请求是否应该缓存,默认是true
if (!request.shouldCache()) {
//将请求添加到mNetworkQueue网络队列中
this.mNetworkQueue.add(request);
return request;
} else {
Map var7 = this.mWaitingRequests;
synchronized(this.mWaitingRequests) {
//获取请求的缓存key,即URL
String cacheKey = request.getCacheKey();
//判断mWaitingRequests队列中是否包含cacheKey
if (this.mWaitingRequests.containsKey(cacheKey)) {
Queue<Request> stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList();
}
((Queue)stagedRequests).add(request);
this.mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", new Object[]{cacheKey});
}
} else {
//将请求添加到mCacheQueue缓存队列中
this.mWaitingRequests.put(cacheKey, (Object)null);
this.mCacheQueue.add(request);
}
return request;
}
}
}
<1> add()方法中有4个主要的队列对象:
mCurrentRequests:用来记录所有任务对象,每有一个网络请求,都会加入到这个队列中,而如果完成任务或者取消任务后,会把这个Request移除队列。
mNetworkQueue:网络请求队列,如果有Request对象加入到这个队列则直接处理。
mCacheQueue:缓存队列,加入队列后会检测有无缓存,如果没有缓存或者是过期缓存则转入到mNetworkQueue队列中。
mWaitingRequests:Map类型,其中可以存储若干个Queue<Request<?>队列,用来存储相同请求的Request对象。
<2> 这里用到了一个设计模式—"生产者消费者模式”,生产者负责生产产品放到阻塞队列中,消费者只负责从阻塞队列中获取产品
4、CacheDispatcher.run()方法
在RequestQueue.add()方法中默认将请求添加到缓存队列(mCacheQueue)中。一直在后台等待的缓存线程就要开始运行起来了。
public class CacheDispatcher extends Thread {
……
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//初始化本地缓存信息
mCache.initialize();
while (true) {
try {
//从缓存阻塞队列中获取request,没有就阻塞
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// 判断request是否被取消,取消就执行下一个
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 根据request从本地缓存中获取缓存对象
Cache.Entry entry = mCache.get(request.getCacheKey());
//判断缓存对象是否为空,是就将request添加到网络缓存队列中,然后执行下一个
if (entry == null) {
request.addMarker("cache-miss");
mNetworkQueue.put(request);
continue;
}
// 判断缓存对象是否过期,是就将request添加到网络缓存队列中,然后执行下一个
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
request.addMarker("cache-hit");
//对数据进行解析
Response<?> response = request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
//判断缓存数据是否需要更新,不需要就将数据发送出去
if (!entry.refreshNeeded()) {
// 将未过期的数据,发送给Response
mDelivery.postResponse(request, response);
} else {
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
response.intermediate = true;
//过期就将request添加到网络缓存队列mNetworkQueue
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) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
}
}
}
<1> 首先可以看到一个while(true)循环,说明缓存线程始终是在运行的,直到调用quit()方法
<2> 然后会尝试从缓存当中取出响应结果,如何为空的话则把这条请求加入到网络请求队列中,如果不为空的话再判断该缓存是否已过期,如果已经过期了则同样把这条请求加入到网络请求队列中,否则就认为不需要重发网络请求,直接使用缓存中的数据即可。如果需要就将请求加入到网络请求队列中
<3> 再之后就会在调用Request的parseNetworkResponse()方法来对数据进行解析。
<4> 将解析的数据通过mDelivery.postResponse()方法回调给主线程
5、NetworkDispatcher.run()方法。
从上面的分析可以看出,如果request不在缓存队列、已经过期、需要更新,那么就会将request添加到网络请求队列(NetworkQueue)中。
public class NetworkDispatcher extends Thread {
……
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request<?> request;
while (true) {
try {
//从网络缓存队列中获取request,没有就阻塞
request = mQueue.take();
} catch (InterruptedException e) {
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// 如果request已经取消,就继续下一个
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
//执行网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
//如果服务器返回304并且已经提交了响应
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
//在工作线程中解析数据
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
//将获取到的数据写入到本地磁盘mCache中
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) {
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
mDelivery.postError(request, new VolleyError(e));
}
}
}
}
<1> 在网络分发器(NetworkDispatcher)中有一个while(true)循环,说明网络请求线程也是在不断运行的。从mNetworkQueue队列中获取request请求
<2> 之后调用mNetwork.performRequest(request)来执行网络请求,这里的mNetwork就是在Volley.newRequestQueue中创建的BasicNetwork对象
<3> 网络请求执行完毕后得到networkResponse,之后就会在调用Request的parseNetworkResponse()方法来对数据进行解析。
<4> 判断request是否需要缓存,是就将数据缓存到磁盘当中
<5> 将解析的数据缓存到本地后通过mDelivery.postResponse()方法回调给主线程
<6> 发生VolleyError或Exception时就会调用mDelivery.postError()方法将错误回调给主线程
private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
error = request.parseNetworkError(error);
mDelivery.postError(request, error);
}
6、 BasicNetwork.performRequest(request) 执行网络请求
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while(true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
HashMap responseHeaders = new HashMap();
try {
Map<String, String> headers = new HashMap();
this.addCacheHeaders(headers, request.getCacheEntry());
//调用mHttpStack执行网络操作
httpResponse = this.mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
//保存响应头信息
Map<String, String> responseHeaders = convertHeaders(httpResponse.getAllHeaders());
if (statusCode == 304) {
return new NetworkResponse(304, request.getCacheEntry().data, responseHeaders, true);
}
//保存响应内容
byte[] responseContents;
if (httpResponse.getEntity() != null) {
//将响应内容保存到内存中即byte[]数组中
responseContents = this.entityToBytes(httpResponse.getEntity());
} else {
responseContents = new byte[0];
}
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
//输出log日志
this.logSlowRequests(requestLifetime, request, responseContents, statusLine);
if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
//请求成功将response信息封装返回
return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
} catch (SocketTimeoutException var12) {
//响应超时调用重试策略
attemptRetryOnException("socket", request, new TimeoutError());
} catch (ConnectTimeoutException var13) {
//请求超时调用重试策略
attemptRetryOnException("connection", request, new TimeoutError());
} catch (MalformedURLException var14) {
throw new RuntimeException("Bad URL " + request.getUrl(), var14);
} catch (IOException var15) {
int statusCode = false;
NetworkResponse networkResponse = null;
if (httpResponse == null) {
throw new NoConnectionError(var15);
}
int statusCode = httpResponse.getStatusLine().getStatusCode();
VolleyLog.e("Unexpected response code %d for %s", new Object[]{statusCode, request.getUrl()});
if (responseContents == null) {
throw new NetworkError(networkResponse);
}
networkResponse = new NetworkResponse(statusCode, (byte[])responseContents, responseHeaders, false);
if (statusCode != 401 && statusCode != 403) {
throw new ServerError(networkResponse);
}
//重试策略
attemptRetryOnException("auth", request, new AuthFailureError(networkResponse));
}
}
}
<1> 该方法最主要的就是调用mHttpStack.performRequest执行网络操作,在android系统大于9时mHttpStack是使用HurlStack,否则是HttpClientStack
<2> 调用entityToBytes方法将响应内容保存到byte[]数组中,在上面的分析知道,使用的是ByteArrayPool数组字节池,提高效率。
/** Reads the contents of HttpEntity into a byte[]. */
private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
PoolingByteArrayOutputStream bytes =
new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());
byte[] buffer = null;
try {
//获取响应数据
InputStream in = entity.getContent();
if (in == null) {
throw new ServerError();
}
//从字节池中获取响应的内存空间
buffer = mPool.getBuf(1024);
int count;
//将数据写入到内存中
while ((count = in.read(buffer)) != -1) {
bytes.write(buffer, 0, count);
}
return bytes.toByteArray();
} finally {
try {
// Close the InputStream and release the resources by "consuming the content".
entity.consumeContent();
} catch (IOException e) {
// This can happen if there was an exception above that left the entity in
// an invalid state.
VolleyLog.v("Error occured when calling consumingContent");
}
//释放
mPool.returnBuf(buffer);
bytes.close();
}
}
<3> 重试策略
org.apache.http.conn.ConnectTimeoutException
连接HTTP服务端超时或者等待HttpConnectionManager返回可用连接超时,俗称请求超时.
java.net.SocketTimeoutException
Socket通信超时,即从服务端读取数据时超时,俗称响应超时.
Volley就是通过捕捉异常来进行超时重试的.
private static void attemptRetryOnException(String logPrefix, Request<?> request,
VolleyError exception) throws VolleyError {
//获取重试实例对象。从上面知道创建request请求的时候,会创建一个默认的重试类DefaultRetryPolicy
RetryPolicy retryPolicy = request.getRetryPolicy();
//获取超时时间,即DefaultRetryPolicy默认的超时时间
int oldTimeout = request.getTimeoutMs();
try {
//调用retry方法重试
retryPolicy.retry(exception);
} catch (VolleyError e) {
request.addMarker(
String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
throw e;
}
request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
}
@Override
public void retry(VolleyError error) throws VolleyError {
//当前重试次数加1
mCurrentRetryCount++;
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (!hasAttemptRemaining()) {
throw error;
}
}
//判断当前重试次数是否比最大次数大,
protected boolean hasAttemptRemaining() {
return mCurrentRetryCount <= mMaxNumRetries;
}
从源码可以看得出重试机制就是通过判断重试次数,如果重试次数小于最大次数就不进行处理,这样就不会跳出循环,继续下次请求。如果重试次数大于最大次数就抛出VolleyError异常,跳出循环。BasicNetwork.performRequest这个方法不会处理这个异常,将这个异常继续往上抛出,抛出给NetworkDispatcher.run()方法,刚才在分析NetworkDispatcher.run()方法的时候,知道捕获到这个VolleyError异常的时候会调用mDelivery.postError()方法将错误回调给主线程,这样就完成了整个重试即过程。
从BasicNetwork.performRequest这个方法没有真正的去执行网络请求,调用了mHttpStack.performRequest方法,下面进到mHttpStack.performRequest方法看看
7、mHttpStack.performRequest()
在创建RequestQueue对象的时候知道,手机系统版本号是大于9的,则创建一个HurlStack的实例,否则就创建一个HttpClientStack的实例
这里以HurlStack为例,看一下内部的实现。
HurlStack.performRequest()
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
//获取请求Url
String url = request.getUrl();
HashMap<String, String> map = new HashMap();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
if (this.mUrlRewriter != null) {
String rewritten = this.mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
//初始化URL和HttpURLConnection
URL parsedUrl = new URL(url);
HttpURLConnection connection = this.openConnection(parsedUrl, request);
Iterator var8 = map.keySet().iterator();
while(var8.hasNext()) {
String headerName = (String)var8.next();
connection.addRequestProperty(headerName, (String)map.get(headerName));
}
//发起网络请求
setConnectionParametersForRequest(connection, request);
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
//获取响应码
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
} else {
//封装响应的信息
StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage());
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
response.setEntity(entityFromConnection(connection));
Iterator var12 = connection.getHeaderFields().entrySet().iterator();
while(var12.hasNext()) {
Entry<String, List<String>> header = (Entry)var12.next();
if (header.getKey() != null) {
Header h = new BasicHeader((String)header.getKey(), (String)((List)header.getValue()).get(0));
//添加响应头
response.addHeader(h);
}
}
return response;
}
}
<1> 这个方法就可以看到内部是使用HttpURLConnection进行网络请求的。进到openConnection方法
private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
HttpURLConnection connection = createConnection(url);
//获取超时时间,即重试机制中的超时时间
int timeoutMs = request.getTimeoutMs();
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);
connection.setUseCaches(false);
connection.setDoInput(true);
// use caller-provided custom SslSocketFactory, if any, for HTTPS
if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
}
return connection;
}
<2> setConnectionParametersForRequest方法
static void setConnectionParametersForRequest(HttpURLConnection connection,
Request<?> request) throws IOException, AuthFailureError {
switch (request.getMethod()) {
case Method.DEPRECATED_GET_OR_POST:
// This is the deprecated way that needs to be handled for backwards compatibility.
// If the request's post body is null, then the assumption is that the request is
// GET. Otherwise, it is assumed that the request is a POST.
byte[] postBody = request.getPostBody();
if (postBody != null) {
// Prepare output. There is no need to set Content-Length explicitly,
// since this is handled by HttpURLConnection using the size of the prepared
// output stream.
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.addRequestProperty(HEADER_CONTENT_TYPE,
request.getPostBodyContentType());
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(postBody);
out.close();
}
break;
case Method.GET:
// Not necessary to set the request method because connection defaults to GET but
// being explicit here.
connection.setRequestMethod("GET");
break;
case Method.DELETE:
connection.setRequestMethod("DELETE");
break;
case Method.POST:
connection.setRequestMethod("POST");
addBodyIfExists(connection, request);
break;
case Method.PUT:
connection.setRequestMethod("PUT");
addBodyIfExists(connection, request);
break;
default:
throw new IllegalStateException("Unknown method type.");
}
}
判断不同的请求方法GET、POST、PUT等等进入不同的逻辑,设置不同的请求方法,看一下post请求,调用了addBodyIfExists方法
private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
throws IOException, AuthFailureError {
//获取请求的body
byte[] body = request.getBody();
if (body != null) {
connection.setDoOutput(true);
connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
//将body写入
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(body);
out.close();
}
}
<3> 请求完毕后就将响应封装并返回。
8、Request.parseNetworkResponse()
从上面的源码分析可知,无论是缓存分发器CacheDispatcher还是网络分发器NetworkDispatcher在执行结束后都会得到NetworkResponse响应对象。之后就会在NetworkDispatcher.run()或CacheDispatcher.run()方法调用Request的parseNetworkResponse(networkResponse)方法来对数据进行解析,这里使用的Request对象就是一开始创建的Request对象。
系统提供的StringRequest/JsonObjectRequest/JsonArrayRequest/ImageRequest或者是自定义的Request。
可以看出如果要自定义Request需要实现parseNetworkResponse。
以StringRequest方法为例,进入源码看一下。
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
将response响应的信息转为String类型,调用Response.success方法。
/** Returns a successful response containing the parsed result. */
public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
return new Response<T>(result, cacheEntry);
}
将响应的结果封装成Response对象返回,拿到Response对象之后,在NetworkDispatcher.run方法中知道如果请求需要缓存的话就将数据进行缓存,
然后就会调用响应分发器ResponseDelivery.postResponse()方法回调给UI线程
9、 ResponseDelivery.postResponse()
在通过Request.parseNetworkResponse()方法拿到需要的response后,需要将得到的数据返回给主线程更新UI,此时用到ResponseDelivery.postResponse()方法。
由于ResponseDelivery是一个接口,它的实现类是 ExecutorDelivery 。在RequestQueue对象创建的时候创建的。
public class ExecutorDelivery implements ResponseDelivery {
private final Executor mResponsePoster;
public ExecutorDelivery(final Handler handler) {
this.mResponsePoster = new Executor() {
public void execute(Runnable command) {
handler.post(command);
}
};
}
public ExecutorDelivery(Executor executor) {
this.mResponsePoster = executor;
}
public void postResponse(Request<?> request, Response<?> response) {
this.postResponse(request, response, (Runnable)null);
}
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, runnable));
}
public void postError(Request<?> request, VolleyError error) {
request.addMarker("post-error");
Response<?> response = Response.error(error);
this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, (Runnable)null));
}
private class ResponseDeliveryRunnable implements Runnable {
private final Request mRequest;
private final Response mResponse;
private final Runnable mRunnable;
public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
this.mRequest = request;
this.mResponse = response;
this.mRunnable = runnable;
}
public void run() {
if (this.mRequest.isCanceled()) {
this.mRequest.finish("canceled-at-delivery");
} else {
if (this.mResponse.isSuccess()) {
this.mRequest.deliverResponse(this.mResponse.result);
} else {
this.mRequest.deliverError(this.mResponse.error);
}
if (this.mResponse.intermediate) {
this.mRequest.addMarker("intermediate-response");
} else {
this.mRequest.finish("done");
}
if (this.mRunnable != null) {
this.mRunnable.run();
}
}
}
}
}
<1> 首先可以看到构造函数中有一个handler参数,该构造函数是在new RequestQueue中初始化的
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
通过Looper.getMainLooper()创建了一个主线程的handler对象并且创建了一个Executor线程池对象
<2> postResponse方法中又调用了Executor.execute方法执行其中的ResponseDeliveryRunnable对象
<3> 在Runnable的run方法中mRequest.deliverResponse()方法。mRequest.deliverResponse也是Request需要实现的方法
<4> 最后通过handler.post()方法将结果返回给主线程更新UI
10、图片加载
Volley提供了ImageRequest、ImageLoader、NetworkImageView用于加载图片。
ImageLoader内部还是用的ImageRequest,NetworkImageView是对ImageLoader的封装。以ImageLoader的源码为例分析一下
<1> 创建ImageLoader对象
imageLoader = new ImageLoader(mQueue, new BitmapCache());
public ImageLoader(RequestQueue queue, ImageCache imageCache) {
mRequestQueue = queue;
mCache = imageCache;
}
public interface ImageCache {
public Bitmap getBitmap(String url);
public void putBitmap(String url, Bitmap bitmap);
}
ImageCache是一个接口,在创建ImageLoader需要去实现这个接口。
有两个方法:
getBitmap:从缓存中获取Bitmap对象
putBitmap:将Bitmap保存到缓存对象中
<2> 创建ImageListener对象
ImageListener listener = ImageLoader.getImageListener(imageView,R.drawable.default_image, R.drawable.failed_image);
public static ImageListener getImageListener(final ImageView view,final int defaultImageResId, final int errorImageResId) {
return new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (errorImageResId != 0) {
view.setImageResource(errorImageResId);
}
}
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
view.setImageBitmap(response.getBitmap());
} else if (defaultImageResId != 0) {
view.setImageResource(defaultImageResId);
}
}
};
}
getImageListener方法中包含三个参数:
ImageView view :图片显示控件
int defaultImageResId : 占位图片资源
int errorImageResId : 错误图片资源
ImageListener监听中有两个方法:
onErrorResponse:响应出错回调,设置错误图片
onResponse: 响应回调,如果图片为空,设置占位图,不为空,显示图片
<3> 调用ImageLoader的get()方法加载网络上的图片
imageLoader.get(“https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg”, listener);
public ImageContainer get(String requestUrl, final ImageListener listener) {
return get(requestUrl, listener, 0, 0);
}
public ImageContainer get(String requestUrl, ImageListener imageListener,int maxWidth, int maxHeight) {
//不是在主线程就抛出异常
throwIfNotOnMainThread();
//获取缓存key
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
//从缓存中获取图片
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
//图片是否为空
if (cachedBitmap != null) {
// 不为空就调用imageListener.onResponse响应
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
//创建ImageContainer对象
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
// 通知imageListener设置占位图
imageListener.onResponse(imageContainer, true);
// Check to see if a request is already in-flight.
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
return imageContainer;
}
// 创建ImageRequest对象
Request<?> newRequest =
new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight,
Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});
//将请求添加到mRequestQueue
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
如果不设置图片的宽高,默认就是0
获取缓存key 调用getCacheKey方法
private static String getCacheKey(String url, int maxWidth, int maxHeight) {
return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
.append("#H").append(maxHeight).append(url).toString();
}
使用的是url和图片的宽高作为key
如果缓存中没有图片图片的话,就创建ImageRequest对象。
public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
Config decodeConfig, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
setRetryPolicy(
new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
mListener = listener;
mDecodeConfig = decodeConfig;
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
}
这里看到图片加载的重试机制默认值和其他request不同。
IMAGE_TIMEOUT_MS : 超时默认是1000
IMAGE_MAX_RETRIES : 最大重试次数是2
public Priority getPriority() {
return Priority.LOW;
}
优先级设置为Low,其他都是NORMAL
在网络获取到图片信息后就会调用ImageRequest.parseNetworkResponse去解析图片信息
protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
// Serialize all decode on a global lock to reduce concurrent heap usage.
synchronized (sDecodeLock) {
try {
//解析响应信息
return doParse(response);
} catch (OutOfMemoryError e) {
VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
return Response.error(new ParseError(e));
}
}
}
private Response<Bitmap> doParse(NetworkResponse response) {
byte[] data = response.data;
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
Bitmap bitmap = null;
if (mMaxWidth == 0 && mMaxHeight == 0) {
decodeOptions.inPreferredConfig = mDecodeConfig;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
} else {
// If we have to resize this image, first get the natural bounds.
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
int actualWidth = decodeOptions.outWidth;
int actualHeight = decodeOptions.outHeight;
// Then compute the dimensions we would ideally like to decode to.
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
actualWidth, actualHeight);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth);
// Decode to the nearest power of two scaling factor.
decodeOptions.inJustDecodeBounds = false;
// TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
// decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
decodeOptions.inSampleSize =
findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
Bitmap tempBitmap =
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
// If necessary, scale down to the maximal acceptable size.
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
tempBitmap.getHeight() > desiredHeight)) {
bitmap = Bitmap.createScaledBitmap(tempBitmap,
desiredWidth, desiredHeight, true);
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
}
if (bitmap == null) {
return Response.error(new ParseError(response));
} else {
return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
}
}
如果不设置图片的宽高,就直接返回。如果设置了图片的宽高信息,就对图片进行压缩处理
处理完成之后就会Response.success或error创建Response对象,通过上面分析最终会回调给 ImageRequest的deliverResponse方法
@Override
protected void deliverResponse(Bitmap response) {
//这个mListener就是在ImageLoader中创建ImageRequest创建的Listener
mListener.onResponse(response);
}
// 创建ImageRequest对象
Request<?> newRequest =
new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight,
Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});
private void onGetImageSuccess(String cacheKey, Bitmap response) {
// 将图片放到缓存中
mCache.putBitmap(cacheKey, response);
BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
if (request != null) {
// Update the response bitmap.
request.mResponseBitmap = response;
// 发送图片信息
batchResponse(cacheKey, request);
}
}
private void batchResponse(String cacheKey, BatchedImageRequest request) {
mBatchedResponses.put(cacheKey, request);
if (mRunnable == null) {
mRunnable = new Runnable() {
@Override
public void run() {
for (BatchedImageRequest bir : mBatchedResponses.values()) {
for (ImageContainer container : bir.mContainers) {
// If one of the callers in the batched request canceled the request
// after the response was received but before it was delivered,
// skip them.
if (container.mListener == null) {
continue;
}
//判断是否有错误
if (bir.getError() == null) {
container.mBitmap = bir.mResponseBitmap;
container.mListener.onResponse(container, false);
} else {
container.mListener.onErrorResponse(bir.getError());
}
}
}
mBatchedResponses.clear();
mRunnable = null;
}
};
// Post the runnable.
mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
}
}
判断是否有错误,没有的话就调用container.mListener.onResponse即ImageListener.onResponse方法去显示图片,否则就container.mListener.onErrorResponse方法显示错误图片
至此整个volley的源码的分析就梳理结束了。
volley 请求重复问题
方案一 对Request设置重试策略时,更改默认超时时间
request.setRetryPolicy( new DefaultRetryPolicy( 500000,//默认超时时间,应设置一个稍微大点儿的,例如本处的500000
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,//默认最大尝试次数
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT ) );
方案二 在HurlStack中的openConnection方法中,在相应段落增加
connection.setChunkedStreamingMode(0)