当我们进行网络请求的时候,可能会面临很多复杂的环境。比如网络环境不好、服务器异常等。
所以当我们使用网络请求框架进行应用开发的时候,一个好的重试机制,可以让我们设定适当的重试次数,不会只请求一次或者一直重试请求无数次,以应对复杂的网络环境;一个好的错误处理机制可以让我们在出现错误的时候,及时作出反馈,不会一直在等待,使应用有一个友好的用户体验。
volley的重试机制的使用是比较简单。
我们可以通过设置RetryPolicy来设置volley的重试次数和请求超时时间。我们可以在使用该框架的时候这样进行设置:
request.setRetryPolicy(new DefaultRetryPolicy(20 *1000, 1, 1.0f));可以看到DefaultRetryPolicy这个类的构造函数:
public DefaultRetryPolicy() {
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
其中第一个参数就是设置超时的时间,第二个参数是设置重试的次数,第三个是一个基数时间,用于算两次重试请求之间的时间间隔。但我们没有进行参数的设置的时候,这三个参数都有一个默认的值,其中超时时间是2500ms,重试次数是1次,基数时间是1。
我们在app开发时这样简单的设置,但是,volley中是如何使用的呢,原理分析会在下面展示。
类:BasicNetWork.java
该类中的performRequest()方法调用HttpStack进行网络请求,并对请求回来的结果进行解析封装,并返回最后的结果。其中重试机制和错误处理机制的使用也是在该方法中进行处理。
方法如下:
@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());
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() == null ? null : request.getCacheEntry().data,
responseHeaders, true);
}
// Handle moved resources
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
String newUrl = responseHeaders.get("Location");
request.setRedirectUrl(newUrl);
}
// Some responses such as 204s do not have content. We must check.
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);
}
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());
} else {
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 if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
attemptRetryOnException("redirect",
request, new AuthFailureError(networkResponse));
} else {
// TODO: Only throw ServerError for 5xx status codes.
throw new ServerError(networkResponse);
}
} else {
throw new NetworkError(networkResponse);
}
}
}
}
可以分析,看到该方法中有一个while(true)的死循环,其实就是为了重试机制的使用(在请求的次数没有达到设置的次数时,会循环进行网络请求的操作)。
httpResponse = mHttpStack.performRequest(request, headers);
这行代码就是真正的进行网络数据请求的代码,并返回得到的数据。然而在请求网络数据的时候,可能会出现异常,然后出现异常是就需要对异常进行处理。 这段代码会抓取SocketTimeoutException、ConnectTimeoutException、MalformedURLException、IOException这几个异常,其中在SocketTimeoutException和ConnectTimeoutException会进行重试的机制调用,就是调用attemptRetryOnException()这个方法。
方法如下:
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));
}
其中RetryPolicy retryPolicy = request.getRetryPolicy();这段代码是其核心。该方法位于DefaultRetryPolicy这个类中。
该方法如下:
@Override
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++;
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (!hasAttemptRemaining()) {
throw error;
}
}
protected boolean hasAttemptRemaining() {
return mCurrentRetryCount <= mMaxNumRetries;
}
可以看到,每次调用这个方法,mCurrentRetryCount就会加一,直到hasAttemptRemaining()方法返回false,就是说重试的次数已经够了,就会抛出VolleyError异常,这时while死循环就会退出。
另外,volley还会根据返回的statusCode,抛出不同的的Error。
代码如下:if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
在上面的performRequest()方法中会catch抛出的IOException,并作出不同的处理。
处理如下:
1、当返回的结果httpResponse为空,则抛出NoConnectionError的异常。
2、当返回结果的内容responseContents为空时,则抛出NetworkError的异常。
3、当返回的结果的内容responseContents不为空时,当statusCode == HttpStatus.SC_UNAUTHORIZED ||statusCode ==HttpStatus.SC_FORBIDDEN时,则会调用attemptRetryOnException()方法进行重试;当statusCode ==HttpStatus.SC_MOVED_PERMANENTLY ||statusCode == HttpStatus.SC_MOVED_TEMPORARILY时,同样调用attemptRetryOnException()方法进行重试;否则会抛出ServerError的异常。
最后,然后我们需要对异常进行捕获,并分发到UI线程进行处理,这就是我们客户端的ErrorListener的回调处理。
在NetworkDispatcher中的run()函数中会会捕获该异常,然后进行分发。
try{
.......
}
catch (VolleyError volleyError) {
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
mDelivery.postError(request, new VolleyError(e));
}
这个过程就是进行网络数据请求时,重试机制和错误处理机制的过程。