Android Volley 超时重试机制

前言:

Volley框架有许多优秀的机制,例如,HTTP缓存策略,内存和磁盘缓存策略,重试策略,四个网络线程一个缓存线程策略。

这里,从源码,解读Volley重试机制。

Volley中,定义出一个重试的RetryPolicy接口:

/**
 * Retry policy for a request.
 *
 * 用途:
 *    1. 重试策略,一定时间,重新发起一个请求。
 *    2. 获取当前时间,当前重试的请求个数
 */
public interface RetryPolicy {

    /**
     * 获取当前重试的时间.
     */
    public int getCurrentTimeout();

    /**
     * 返回当前重试次数
     */
    public int getCurrentRetryCount();
    /**
     * 当应用超时的时候,准备下一次的重试
     * @param error 上一次尝试发生的异常
     * @throws VolleyError 当尝试不能执行,会抛出异常
     */
    public void retry(VolleyError error) throws VolleyError;
}

接下来,看RetryPolicy接口的实现类DefaultRetryPolicy。

/**
 * Default retry policy for requests.
 * 用途:
 *     请求中默认的重试策略
 *     时间,重试次数,回退的乘数
 */
public class DefaultRetryPolicy implements RetryPolicy {
    /** 当前超时累计的时间(毫秒为单位) */
    private int mCurrentTimeoutMs;

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

    /** 最大重试次数 */
    private final int mMaxNumRetries;

    /** 超时的乘数因子 */
    private final float mBackoffMultiplier;

    /** 默认的重试一次的时间*/
    public static final int DEFAULT_TIMEOUT_MS = 2500;

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

    /**
     *  默认的超时的乘数因子
     *
     *  当前的重试时间=上一次超时时间+(上一次的超时时间*乘数因子)
     *
     *  例如: 乘数因子为1 ,一次重试时间为2.5秒 ,最大的重试次数为2
     *   第一次重试: 重试时间=2.5+2.5*1=5秒
     *   第二次重试: 重试时间=5+5*1=10秒
     * */
    public static final float DEFAULT_BACKOFF_MULT = 1f;
    /**
     * 使用默认的重试策略
     */
    public DefaultRetryPolicy() {
        this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
    }
    /**
     *
     *
     *  参数1:策略执行时间
     *  参数2:在执行时间内,重试次数
     *  参数3:回退的乘数。 当前时间+=(当前时间*参数3)
     *
     */
    public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
        mCurrentTimeoutMs = initialTimeoutMs;
        mMaxNumRetries = maxNumRetries;
        mBackoffMultiplier = backoffMultiplier;
    }
    /**
     * 返回当前重试的累加时间.
     */
    @Override
    public int getCurrentTimeout() {
        return mCurrentTimeoutMs;
    }

    /**
     * 返回当前的重试次数,第几次重试
     */
    @Override
    public int getCurrentRetryCount() {
        return mCurrentRetryCount;
    }
    /**
     * 重试策略执行逻辑
     */
    @Override
    public void retry(VolleyError error) throws VolleyError {
        //累加尝试次数
        mCurrentRetryCount++;
        //累加重试时间
        mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
        if (!hasAttemptRemaining()) {//当前的重试次数大于指定重试次数,抛出该异常
            throw error;
        }
    }
    /**
     *  返回true ,则请求还有重试次数
     */
    protected boolean hasAttemptRemaining() {
        return mCurrentRetryCount <= mMaxNumRetries;
    }
}

从以上代码可知: 重试时间=上次重试时间+上次重试时间*乘数因子。

设置了重试时间,但需要作用在Http请求上才有效。

接下来,看下重试策略如何作用在HttpUrlConnection的连接时间和响应时间。

找到HurlStack类,该类是执行HttpUrlConnection的逻辑操作类。

public class HurlStack implements HttpStack {

     //...省略部分代码

    /**
     * 根据url中带有的协议,来开启一个带有参数的HttpURLConnection,或者HttpsURLConnection
     */
    private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
        HttpURLConnection connection = createConnection(url);
        //获取Request中的指定时间
        int timeoutMs = request.getTimeoutMs();
        //设置连接时间
        connection.setConnectTimeout(timeoutMs);
        //设置读取时间
        connection.setReadTimeout(timeoutMs);
        //...省略部分代码
        return connection;
    }

}

可以发现设置的响应时间和连接时间都是Request中获取的。

接下来,找到Request类:

public abstract class Request<T> implements Comparable<Request<T>> {
       public Request(int method, String url, Response.ErrorListener listener) {
       //....省略部分代码
        setRetryPolicy(new DefaultRetryPolicy());
    }
    /**
     * 设置重试策略
     */
    public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
        mRetryPolicy = retryPolicy;
        return this;
    }
    /**
     * 重试策略中的当前累加重试时间
     */
    public final int getTimeoutMs() {
        return mRetryPolicy.getCurrentTimeout();
    }
}

发现,Request这个超类已经默认设置了重试策略。

以上代码只是实现了重试机制的时间组作用在Http请求上,但如何计算重试机制的逻辑并没有实现,接下来查看Volley如何计算重试次数。

找到BasicNetwork 类:for循环方式,累加重试

public class BasicNetwork implements Network {
      /**
     * 执行网络请求,返回响应数据
     *
     * @param request Request to process
     * @return
     * @throws VolleyError 执行网络请求,for循环的方式,执行重试策略。
     *                     <p>
     *                     若是执行成功或者重试策略执行完抛出异常,跳出for循环。
     */
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        //引导后的毫秒数(包含睡眠花费的时间)
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
                //执行Http请求,每次循环都执行最新的重试时间
                mHttpStack.performRequest(request, headers);

                //若是服务器返回状态码在小于200或者待遇299时,抛出一个异常
                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 (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);
                }
            }
        }
    }

   /**
     * 执行,重试策略。
     * <p>
     * 若是请求中已经没有更多的重试策略,抛出这次请求网络的异常。
     *
     * @param request The request to use.
     */
    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));
    } 
}

从以上代码可知: 每次执行Http请求都会捕捉SocketTimeoutExceptionConnectTimeoutException和服务器验证的重试错误。当发生这几个异常的时候,会走attemptRetryOnException(),执行一次重试操作,并没有跳出for循环。当执行完网络请求会return跳出,或者重试策略执行完throw异常跳出。

成功执行完Request请求或者抛出异常,后并没有停止执行,而是传递结果到监听器中。

找到 NetworkDispatcher类:

public class NetworkDispatcher extends Thread {

      @Override
    public void run() {
        //设置线程优先级,这里是后台线程
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request<?> request;
        //while循环,从网络队列中获取要执行的请求。
        while (true) {
            try {
                // 从网络队列中获取一个请求
                request = mQueue.take();
            } catch (InterruptedException e) {
              //当队列中抛出一个异常,且程序需要关闭网络线程池,则停止该线程。
                if (mQuit) {
                    return;
                }
                continue;
            }
            try {
               //....省略部分代码
                //在NetWork子类类中执行网络请求的操作,返回网络响应数据
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                //....省略部分代码

                //在网络线程中指向解析响应的数据
                Response<?> response = request.parseNetworkResponse(networkResponse);

                 //....省略部分代码
                //在ResponseDelivery类中回调请求和解析后响应数据
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                mDelivery.postError(request, new VolleyError(e));
            }
        }
    }
    /**
     * 解析,传递网络异常。
     * @param request
     * @param error
     */
    private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
        error = request.parseNetworkError(error);
        mDelivery.postError(request, error);
    }  
}

BaseNetWork执行完Request重试策略后。抛出的异常会被 NetworkDispatcher线程中捕捉到,然后通过ResponseDelivery执行在主线程中回调给监听器。执行成功后服务器返回的Response数据会被解析,解析的结果通过通过ResponseDelivery执行在主线程中回调给监听器。

猜你喜欢

转载自blog.csdn.net/hexingen/article/details/81235770
今日推荐