Volley框架的错误重试机制分析

Volley框架的网络处理

相关类如下:
Network :封装网络请求接口
BasicNetWork : Network 接口实现类,用于封装网络请求,并且对请求结果封装,且实现网络请求错误重试机制。
HttpStack:执行网络请求接口
HurlStack:HttpStack 接口实现类,用于真正执行网络请求,并且得到请求结果。
HttpClientStack:HttpStack 接口实现类,同样用于真正执行网络请求,并且得到请求结果。

HurlStack 和 HttpClientStack 都是执行网络请求,得到请求结果的类,区别在于HurlStack是基于HttpURLConnection库实现的,而HttpClientStack是基于HttpClient库实现的。由此我们也可以用其他的网络库来实现Volley框架的网络请求。

Network 和 HurlStack 的关系可以这么理解:Network用于Volley框架唯一的一个网络请求接口对外暴露,而HurlStack是网络请求的不同实现,可以有多种不同的网络请求实现方式。可以理解Network是一个对外的抽象接口,而HurlStack是一个内在具体实现接口。

接下来具体分析下BaseNetwork类的实现

public class BasicNetwork implements Network {
   ..........
    protected final HttpStack mHttpStack;

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

    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        //记录开始请求的时间
        long requestStart = SystemClock.elapsedRealtime();
        long requestStart = SystemClock.elapsedRealtime();
        //死循环,用于错误重试机制
        while (true) {
            //标准的网络请求结果
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            //请求头
            Map<String, String> responseHeaders = Collections.emptyMap();
            try {
                // 封装请求头
                Map<String, String> headers = new HashMap<String, String>();
                addCacheHeaders(headers, request.getCacheEntry());
                //此处执行真正的网络请求操作,具体实现请看HurlStack或者HttpClientStack类的实现。
                httpResponse = mHttpStack.performRequest(request, headers);
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();
                //解析请求结果头
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                // 控制本地缓存验证,返回状态码是否等于304,状态304表示该服务器资源未被修改过。
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                    Cache.Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                                responseHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);
                    }

                   //替换缓存实体的请求结果头信息.
                    entry.responseHeaders.putAll(responseHeaders);
                    //当服务器返回304状态码时,不解析请求结果,直接使用缓存结果即可。
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                }

                // 解析网络请求结果实体
                if (httpResponse.getEntity() != null) {
                  responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                  // 如果请求返回null,则封装一个空字节。
                  responseContents = new byte[0];
                }

                // 打印超过3s的网络请求,这点方便开发调试
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);
                //如果服务器返回状态码不在200~299之间,则抛出异常,注意:下面代码有捕获异常。(此处就是错误重试机制的开始)
                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }
                //正常情况下,返回网络请求结果。
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                        SystemClock.elapsedRealtime() - requestStart);
            } catch (SocketTimeoutException e) {
                //服务器超时异常,触发错误重试机制
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException e) {
                //连接超时异常,触发错误重试机制
                attemptRetryOnException("connection", request, new TimeoutError());
            } catch (MalformedURLException e) {
                //无效的Url,抛出异常,并没有代码去捕获它,因此会跳出死循环。
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {//捕获异常处
                int statusCode;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusLine().getStatusCode();
                } else {
                    //如果服务器返回结果为null,则表示服务器连接错误,直接跳出死循环,结束重试。
                    throw new NoConnectionError(e);
                }
                VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                NetworkResponse networkResponse;
                if (responseContents != null) {
                    //封装错误网络请求结果,以便重试机制结束后将错误结果分发出去。
                    networkResponse = new NetworkResponse(statusCode, responseContents,
                            responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                    //如果返回状态码是被禁止的或者未授权的,则尝试重试机制。
                    if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                            statusCode == HttpStatus.SC_FORBIDDEN) {
                        attemptRetryOnException("auth",
                                request, new AuthFailureError(networkResponse));
                    } else if (statusCode >= 400 && statusCode <= 499) {
                        // 状态码在该区间,表示客户端错误,直接抛出异常,跳出死循环,结束访问。
                        throw new ClientError(networkResponse);
                    } else if (statusCode >= 500 && statusCode <= 599) {
                        //状态码在该区间,表示服务端异常,如果有设置服务端异常重试标记,则尝试重试机制。默认情况下是没有不对服务器错误进行重试的。不过可以设置。
                        if (request.shouldRetryServerErrors()) {
                            attemptRetryOnException("server",
                                    request, new ServerError(networkResponse));
                        } else {
                            throw new ServerError(networkResponse);
                        }
                    } else {
                        // 3xx? No reason to retry.
                        //其他错误,直接抛出异常,跳出死循环,结束访问。
                        throw new ServerError(networkResponse);
                    }
                } else {
                    //其他情况下,尝试错误重试机制。
                    attemptRetryOnException("network", request, new NetworkError());
                }
            }
        }
    }
    ......................
}

分析:以上代码几乎每行都有注释,已经解释的很清楚了。跟着代码和注释走一遍,相信你也可以理解。以上代码主要做了两件事情:

  1. 执行网络请求操作,主要体现在代码第26行;mHttpStack对象的具体实现在HurlStack或者HttpClientStack类。
  2. 错误重试机制,主要体现在各种异常处理和attemptRetryOnException()方法中。

执行网络请求的具体实现HurlStack 和 HttpClientStack类其实没什么好讲的,里面实现的performRequest方法主要还是一般的网络请求处理代码。这里主要分析下Volley框架的错误重试机制。

有以上代码也可知,Volley框架实现的错误重试机制是在异常中进行处理的。具体怎么实现重试机制的,还得看看attemptRetryOnException()方法。

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 类的retry()方法而已,而且我们知道RetryPolicy 只是定义的一个重试机制的接口,具体实现在DefaultRetryPolicy类中。我们来瞧瞧DefaultRetryPolicy类的具体实现。

DefaultRetryPolicy重试机制

public class DefaultRetryPolicy implements RetryPolicy {
    /** 当前超时时间,单位ms. */
    private int mCurrentTimeoutMs;

    /** 当前重试次数. */
    private int mCurrentRetryCount;

    /** 最大的尝试次数. */
    private final int mMaxNumRetries;

    /** 重试时间增长因子. */
    private final float mBackoffMultiplier;

    /** 默认socket超时时间 */
    public static final int DEFAULT_TIMEOUT_MS = 2500;

    /** 默认重试次数 */
    public static final int DEFAULT_MAX_RETRIES = 1;

    /** 默认的超时增长因子 */
    public static final float DEFAULT_BACKOFF_MULT = 1f;

    /**
     * 默认构造方法,使用默认的超时为2500ms,默认重试次数为1次,默认增长因此为1.
     */
    public DefaultRetryPolicy() {
        this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
    }

    /**
     * Constructs a new retry policy.
     * @param initialTimeoutMs The initial timeout for the policy.
     * @param maxNumRetries The maximum number of retries.
     * @param backoffMultiplier Backoff multiplier for the policy.
     */
    public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
        mCurrentTimeoutMs = initialTimeoutMs;
        mMaxNumRetries = maxNumRetries;
        mBackoffMultiplier = backoffMultiplier;
    }

    /**
     * Returns the current timeout.
     */
    @Override
    public int getCurrentTimeout() {
        return mCurrentTimeoutMs;
    }

    /**
     * Returns the current retry count.
     */
    @Override
    public int getCurrentRetryCount() {
        return mCurrentRetryCount;
    }

    /**
     * Returns the backoff multiplier for the policy.
     */
    public float getBackoffMultiplier() {
        return mBackoffMultiplier;
    }

    /**
     * Prepares for the next retry by applying a backoff to the timeout.
     * @param error The error code of the last attempt.
     */
    @Override
    public void retry( VolleyError error) throws VolleyError {
        //增加一次重试次数
        mCurrentRetryCount++;
        //重写计算超时时间
        mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
        //判断是否结束重试
        if (!hasAttemptRemaining()) {
            throw error;
        }
    }

    /**
     * Returns true if this policy has attempts remaining, false otherwise.
     */
    protected boolean hasAttemptRemaining() {
        //当前重试次数是否小于最大重试次数,由此来判断重试是否结束。
        return mCurrentRetryCount <= mMaxNumRetries;
    }
}

分析:
以上代码最主要的部分就是retry()方法的实现,在该方法实现中,只要调用过该方法一次就记录一次当前重试次数,并且根据增长因子从新计算访问网络的超时时间。然后再调用hasAttemptRemaining()方法来判断重试次数是否结束,如果结束就抛出异常,此处抛出的异常会有BasicNetwork#attemptRetryOnException方法来捕获,而attemptRetryOnException方法捕获到异常也会抛出,最终由BasicNetwork#performRequest方法捕获异常,结束整个重试机制,结束网络访问。

总结:Volley的重试机制实现很巧妙,通过在访问网络时实现一个死循环,当访问网络异常时,利用抛出和捕获不同的异常来进行下一次的网络访问来达到重试机制的目的或者跳出死循环来结束网络访问。而我们可以完全指定网络超时时间,超时增长因子,重试次数。虽然Volley框架的重试机制在访问网络中实现,但是RetryPolicy和BasicNetWork的耦合性低,便于用户自定义重试机制。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值