volley的重试机制及错误处理机制

当我们进行网络请求的时候,可能会面临很多复杂的环境。比如网络环境不好、服务器异常等。

所以当我们使用网络请求框架进行应用开发的时候,一个好的重试机制,可以让我们设定适当的重试次数,不会只请求一次或者一直重试请求无数次,以应对复杂的网络环境;一个好的错误处理机制可以让我们在出现错误的时候,及时作出反馈,不会一直在等待,使应用有一个友好的用户体验。


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

这个过程就是进行网络数据请求时,重试机制和错误处理机制的过程。







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值