中转站volleyAndroid知识

重温Volley源码(二):重试策略

2017-04-21  本文已影响72人  守望君

目录

一、核心类

二、重试策略

参考资料

本文是建立在对Volley框架的工作流程有一定基础了解的情况下继续深入的,这里顺便贴上我写的上篇文章《重温Volley源码(一):工作流程》 ,欢迎阅读指正。

一、核心类

RetryPolicy:Volley定义的请求重试策略接口


public interface RetryPolicy {

    /**当前请求的超时时间
     * Returns the current timeout (used for logging).
     */
    public int getCurrentTimeout();

    /**当前请求重试的次数
     * Returns the current retry count (used for logging).
     */
    public int getCurrentRetryCount();

    /**在请求异常时调用此方法
     * Prepares for the next retry by applying a backoff to the timeout.
     * @param error The error code of the last attempt.
     * @throws VolleyError In the event that the retry could not be performed (for example if we
     * ran out of attempts), the passed in error is thrown.
     */
    public void retry(VolleyError error) throws VolleyError;
}

DefaultRetryPolicy:RetryPolicy的实现子类


public class DefaultRetryPolicy implements RetryPolicy {
    /** The current timeout in milliseconds. 当前超时时间*/
    private int mCurrentTimeoutMs; 

    /** The current retry count. 当前重试次数*/
    private int mCurrentRetryCount;

    /** The maximum number of attempts. 最大重试次数*/
    private final int mMaxNumRetries;

    /** The backoff multiplier for the policy. 超时时间的乘积因子*/
    private final float mBackoffMultiplier;

    /** The default socket timeout in milliseconds 默认超时时间*/
    public static final int DEFAULT_TIMEOUT_MS = 2500;

    /** The default number of retries 默认的重试次数*/
    public static final int DEFAULT_MAX_RETRIES = 0;

    /** The default backoff multiplier 默认超时时间的乘积因子*/
    /**
    *   以默认超时时间为2.5s为例
    *   DEFAULT_BACKOFF_MULT = 1f, 则每次HttpUrlConnection设置的超时时间都是2.5s*1f*mCurrentRetryCount
    *   DEFAULT_BACKOFF_MULT = 2f, 则第二次超时时间为:2.5s+2.5s*2=7.5s,第三次超时时间为:7.5s+7.5s*2=22.5s
    */
    public static final float DEFAULT_BACKOFF_MULT = 1f;


    /**
     * Constructs a new retry policy using the default timeouts.
     */
    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);
        //判断是否还有剩余次数,如果没有则抛出VolleyError异常
        if (!hasAttemptRemaining()) {
            throw error;
        }
    }

    /**
     * 判断当前Request的重试次数是否超过最大重试次数
     * Returns true if this policy has attempts remaining, false otherwise.
     */
    protected boolean hasAttemptRemaining() {
        return mCurrentRetryCount <= mMaxNumRetries;
    }
}

二、重试策略

在深入源码阅读你会发现,retry方法会抛出VolleyError的异常,但该方法内部并不是重新发起了网络请求,而是变更重试策略的属性,如超时时间和重试次数,当超过重试策略设定的限定就会抛异常,这个可以在DefaultRetryPolicy里得到验证。那么它究竟是如何做到重试的呢,我们可以跟踪源码 retry 方法被调用到的地方,来到了BasicNetwork的attemptRetryOnException方法:


public class BasicNetwork implements Network {

......

    /**
    *  尝试对一个请求进行重试策略,
    */
    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));
            //当仍然可以进行重试的时候,不会执行到catch语句,但是当执行到catch语句的时候,表示已经不能进行重试了,就抛出异常  中断while(true)循环
            throw e;
        }
        //给请求添加标记,请求了多少次  
        request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
    }

}

继续跟踪attemptRetryOnException方法被调用的地方,来到了BasicNetwork的performRequest方法:


public class BasicNetwork implements Network {

......

    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            ......

            try {
                ......

                //如果发生了超时、认证失败等错误,进行重试操作,直到成功。若attemptRetryOnException再抛出异常则结束
                //当catch后没有执行上面的return,而当前又是一个while(true)循环,可以保证下面的请求重试的执行,是利用循环进行请求重试,请求重试策略只是记录重试的次数、超时时间等内容
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                        SystemClock.elapsedRealtime() - requestStart);
            } catch (SocketTimeoutException e) {
                //1.尝试进行请求重试
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException e) {
                //2.尝试进行请求重试
                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, SystemClock.elapsedRealtime() - requestStart);
                    if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                            statusCode == HttpStatus.SC_FORBIDDEN) {
                        //3.尝试进行请求重试
                        attemptRetryOnException("auth",
                                request, new AuthFailureError(networkResponse));
                    } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || 
                                statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                        //4.尝试进行请求重试
                        attemptRetryOnException("redirect",
                                request, new RedirectError(networkResponse));
                    } else {
                        // TODO: Only throw ServerError for 5xx status codes.
                        throw new ServerError(networkResponse);
                    }
                } else {
                    throw new NetworkError(e);
                }
            }
        }
    }

}

performRequest这个方法名是否很眼熟?是的,它曾被我在上一篇文章中简单提到过,还记得在NetworkDispatcher的run方法中,mNetWork对象(即BasicNetwork)会调用performRequest来执行请求,在该方法内部又是一个while(true)循环

在这个方法里attemptRetryOnException总共被调用了四次:

现在我们归纳一下,首先假设我们设置了请求重试的策略(Volley默认最大请求重试次数为0,即不重试),其次BasicNetwork的performRequest方法的外面其实是个while循环,假如在网络请求过程中发生了异常,如超时、认证未通过等上述四个被调用的异常,catch这个异常的代码会看看是否可以重试,如果可以重试,就会把这个异常吞噬掉,然后进入下一次循环,否则超过重试次数,attemptRetryOnException内部再抛出异常,此时交由上一层代码去处理,并退出while循环。

可能还有个疑问,上面说的交由上一层代码去处理是到底是怎么处理并退出while循环的?这里因为BasicNetwork的performRequest方法并没有捕获VolleyError异常,因此没有被try&catch住的异常会继续往外抛出,这里我们回过头来看看NetworkDispatcher的run方法里头:

public class NetworkDispatcher extends Thread {

    ......

    @Override
    public void run() {

        ......

        while (true) {

            ......

            try {

                ......


                // Perform the network request. 真正执行网络请求的地方,BasicNetwork超时抛出的VolleyError最终会抛出到这里
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                
                ......

                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                // 捕获VolleyError异常,通过主线程Handler回调用户设置的ErrorListener中的onErrorResponse回调方法
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
            }
        }
    }
}

Request无法继续重试后抛出的VolleyError异常,会被NetworkDispatcher捕获,然后利用Delivery去回调用户设置的ErrorListener。

参考资料

上一篇下一篇

猜你喜欢

热点阅读