Volley框架的基本解读(三)

时间:2021-11-07 15:08:14

上一篇Volley框架的基本解读(二)中,我们说到了NetworkDispatcher的run方法,查看源码我们发现,NetworkDispatcher只负责调度并不负责具体的网络请求,它相当于一个中转站,只是将request从RequestQueue手中取来,然后原封不动的交给mNetwork,那么问题来了,mNetwork是什么鬼?我们看源码:


public interface Network {
/**
* Performs the specified request.
* @param request Request to process
* @return A {@link NetworkResponse} with data and caching metadata; will never be null
* @throws VolleyError on errors
*
* 执行request
*/
public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}

很显然这是一个接口,里面有一个抽象方法,用于执行请求。问题又来了,这个接口的具体实现是什么?什么时候被创建的?让我们回想一下,当我们调用Volley.newRequestQueue方法时,里面是不是有这么一句话:


// 网络接口的基本实现,处理网络请求或失败重试
Network network = new BasicNetwork(stack);


随后这个network便以参数的形式传给了RequestQueue,然后RequestQueue又传给NetworkDispatcher,这条线也就清晰了,我们要找的Network的具体实现正是BasicNetwork,发源码证明一下:


public class BasicNetwork implements Network


在看performRequest这个抽象方法的具体实现之前,我们先看看它的构造方法:


public BasicNetwork(HttpStack httpStack) {
// If a pool isn't passed in, then build a small default pool that will give us a lot of
// benefit and not use too much memory.
this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
}


HttpStack正是Volley.newRequestQueue中创建传入的,可见这里又创建了ByteArrayPool缓存池,大小为4096,也就是4KB,然后调用了另一个构造方法:


public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
mHttpStack = httpStack;
mPool = pool;
}

这里没什么好说的,我们来看抽象方法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 = new HashMap<String, String>();
try {
// Gather headers.
Map<String, String> headers = new HashMap<String, String>();
addCacheHeaders(headers, request.getCacheEntry());
// 真正执行网络请求的其实是mHttpStack
httpResponse = mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();

responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// Handle cache validation.
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {// 无修改
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
request.getCacheEntry().data, responseHeaders, true);
}

// Some responses such as 204s do not have content. We must check.
// 可能会发生204无内容返回,因此我们必须检查
if (httpResponse.getEntity() != null) {
responseContents = entityToBytes(httpResponse.getEntity());
} else {
// Add 0 byte response as a way of honestly representing a
// no-content request.
responseContents = new byte[0];
}

// if the request is slow, log it.
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
// 调试信息
logSlowRequests(requestLifetime, request, responseContents, statusLine);

if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
} catch (SocketTimeoutException e) {
attemptRetryOnException("socket", request, new TimeoutError());
} catch (ConnectTimeoutException e) {
attemptRetryOnException("connection", request, new TimeoutError());
} catch (MalformedURLException e) {
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {
int statusCode = 0;
NetworkResponse networkResponse = null;
if (httpResponse != null) {
statusCode = httpResponse.getStatusLine().getStatusCode();
} else {
throw new NoConnectionError(e);
}
VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
if (responseContents != null) {
networkResponse = new NetworkResponse(statusCode, responseContents,
responseHeaders, false);
if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
statusCode == HttpStatus.SC_FORBIDDEN) {
attemptRetryOnException("auth",
request, new AuthFailureError(networkResponse));
} else {
// TODO: Only throw ServerError for 5xx status codes.
throw new ServerError(networkResponse);
}
} else {
throw new NetworkError(networkResponse);
}
}
}
}

代码比较长,大家先别晕,因为这里还只是对网络请求返回数据的封装处理而已,还记得我们在NetworkResponse的run方法中,BasicNetwork调用上面这个方法,返回的是什么吗?答案是NetworkResponse,这个类是一个实体类,封装返回数据:


public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers,
boolean notModified) {
this.statusCode = statusCode;
this.data = data;
this.headers = headers;
this.notModified = notModified;
}

封装的参数分别是状态码,返回数据,响应头,无修改标记。


知道了这个后,我们再来分析performRequest,里面同样是一个while死循环,在第12行addCacheHeaders方法用于添加缓存请求头,然后mHttpStack调用同名方法进行网络请求,这里mHttpStack就相当于BasicNetwork的代理,返回HttpResponse,接下来大家应该都很熟悉了,得到状态行,再通过状态行得到状态码,调用convertHeaders方法得到响应头:


/**
* Converts Headers[] to Map<String, String>.
*
* 获取响应头
*/
private static Map<String, String> convertHeaders(Header[] headers) {
Map<String, String> result = new HashMap<String, String>();
for (int i = 0; i < headers.length; i++) {
result.put(headers[i].getName(), headers[i].getValue());
}
return result;
}

之后判断状态码是否是304无修改,作出返回,判断状态码是否是204无内容,否读出内容,是给一个0长度的字节数组,logSlowRequests方法不用管,是调试信息,状态码不正确抛出IOException,经过这些判断之后,封装成NetworkResponse给予返回。


流程走通之后,发现也没有那么难,有木有?


上面说的是成功流程,我们再来看看异常流程,该方法一共捕抓了4个异常,SocketTimeoutException与ConnectTimeoutException调用了attemptRetryOnException方法:


/**
* Attempts to prepare the request for a retry. If there are no more attempts remaining in the
* request's retry policy, a timeout exception is thrown.
* @param request The request to use.
*
* 尝试重试请求,重试策略由RetryPolicy来决定
*/
private static void attemptRetryOnException(String logPrefix, Request<?> request,
VolleyError exception) throws VolleyError {
RetryPolicy retryPolicy = request.getRetryPolicy();
int oldTimeout = request.getTimeoutMs();

try {
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));
}

细心的读者可能已经发现这个方法会抛出一个VolleyError异常,而这个异常在performRequest方法中并没有捕抓,同样将它抛了出去,还记得我们再说NetworkDispatcher的run时,里面捕抓了一个VolleyError异常吧,正是这里可能出抛出的。


RetryPolicy是一个重试策略类,它由request创建时,在构造方法中被创建:


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();
}

上面调用了retryPolicy.retry(exception),我们看看DefaultRetryPolicy该类的实现:


/**
* Prepares for the next retry by applying a backoff to the timeout.
* @param error The error code of the last attempt.
*
* 这里是重试策略,网络执行BasicNetwork中,网络请求是一个死循环,只有请求成功或抛出异常能够跳出
*/
@Override
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++;
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
// 当前请求次数超过了最大请求次数,就抛出异常,由BasicNetwork再次抛给NetworkDispatcher,
// 在NetworkDispatcher中做出异常回调
if (!hasAttemptRemaining()) {
throw error;
}
}

/**
* Returns true if this policy has attempts remaining, false otherwise.
*/
protected boolean hasAttemptRemaining() {
return mCurrentRetryCount <= mMaxNumRetries;
}

mCurrentRetryCount是当前的请求次数,默认为0,mCurrentTimeoutMs是当前的超时时间,用于调试之用,我们可以无视,mMaxNumRetries是最大的尝试次数,默认为1。


到这里可能已经有人走通了,如果没有,我们再理理思路,retry方法调起,mCurrentRetryCount变为1,hasAttemptRemaining方法返回为true,BasicNetwork类中performRequest方法中while死循环就无法跳出,继续一次请求,如果再次失败,mCurrentRetryCount变为2,hasAttemptRemaining方法返回为false,抛出异常,attemptRetryOnException方法捕抓后,同样抛出,BasicNetwork再次抛出,由NetworkDispatcher来捕抓该异常,执行错误回调。


这错综复杂的线就通了。


我们接着说,MalformedURLException就是指URL错误,直接抛出RuntimeException。


IOException又分几种详细的错误,判断是否返回HttpResponse,否抛出无连接异常,判断数据是否不为空,否抛出网络异常,是封装NetworkResponse,判断401或403错误,是尝试再次请求,否抛出服务器错误。


这里值得一提的是,封装的NetworkResponse就是服务器返回的错误信息,我们可以通过VolleyError.networkResponse.data拿到它。


好了,我们下一篇博客再见。