Volley超时重试机制详解

Volley超时重试机制


基础用法

Volley为开发者提供了可配置的超时重试机制,我们在使用时只需要为我们的Request设置自定义的RetryPolicy即可.

参考设置代码如下:

int DEFAULT_TIMEOUT_MS = 10000;
int DEFAULT_MAX_RETRIES = 3;
StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
    @Override
    public void onResponse(String s) {
        LogUtil.i(TAG, "res=" + s);
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError volleyError) {
        LogUtil.e(TAG, volleyError.toString());
    }
});
// 设置Volley超时重试策略
stringRequest.setRetryPolicy(new DefaultRetryPolicy(
        DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
RequestQueue requestQueue = VolleyManager.getInstance(context).getRequestQueue();
requestQueue.add(stringRequest);

基础知识

在讲解Volley的超时重试原理之前,需要先普及一下跟超时重试相关的异常.

org.apache.http.conn.ConnectTimeoutException

A timeout while connecting to an HTTP server or waiting for an available connection from an HttpConnectionManager.

连接HTTP服务端超时或者等待HttpConnectionManager返回可用连接超时,俗称请求超时.

java.net.SocketTimeoutException

Signals that a timeout has occurred on a socket read or accept.

Socket通信超时,即从服务端读取数据时超时,俗称响应超时.

Volley就是通过捕捉这两个异常来进行超时重试的.


Volley捕捉超时异常

看过之前Volley源码分析的同学,应该知道Volley中是通过BasicNetwork去执行网络请求的.相关源码如下:

@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
    // 记录请求开始的时间,便于进行超时重试
    long requestStart = SystemClock.elapsedRealtime();
    while (true) {
        HttpResponse httpResponse = null;
        byte[] responseContents = null;
        Map<String, String> responseHeaders = Collections.emptyMap();
        try {
            // ......省略部分添加HTTP-HEADER的代码

            // 调用HurlStack的performRequest方法执行网络请求, 并将请求结果存入httpResponse变量中
            httpResponse = mHttpStack.performRequest(request, headers);

            StatusLine statusLine = httpResponse.getStatusLine();
            int statusCode = statusLine.getStatusCode();
            responseHeaders = convertHeaders(httpResponse.getAllHeaders());

            // ......省略部分对返回值处理的代码
            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 (IOException e) {
            // 省略对IOException的异常处理,不考虑服务端错误的重试机制
        }
    }
}
private void attemptRetryOnException(String logPrefix, Request<?> request, VolleyError exception) throws VolleyError{
    RetryPolicy retryPolicy = request.getRetryPolicy();
    int oldTimeout = request.getTimeoutMs();

    retryPolicy.retry(exception);
    Log.e("Volley", String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
}

因为BasicNetwork类中是用java while(true)包裹连接请求,因此如果捕捉到程序抛出SocketTimeoutException或者ConnectTimeoutException,并不会跳出退出循环的操作,而是进入到attemptRetryOnException方法.

如果attemptRetryOnException方法中没有抛出VolleyError异常,最终程序还是可以再次进入while循环,从而完成超时重试机制.

接下来,我们看一下RetryPolicy是如何判断是否需要进行超时重试,并且是如何停止超时重试的.


RetryPolicy.java

RetryPolicy是一个接口定义,中文注释的源码如下:

/**
 * Request请求的重试策略类.
 */
@SuppressWarnings("unused")
public interface RetryPolicy {
    /**
     * 获取当前请求的超时时间
     */
    int getCurrentTimeout();

    /**
     * 获取当前请求的重试次数
     */
    int getCurrentRetryCount();

    /**
     * 实现类需要重点实现的方法,用于判断当前Request是否还需要再进行重试操作
     */
    void retry(VolleyError error) throws VolleyError;
}

RetryPolicy只是Volley定义的Request请求重试策略接口,同时也提供了DefaultRetryPolicy实现类来帮助开发者来快速实现自定制的请求重试功能.


DefaultRetryPolicy.java

中文注释的源码如下:

public class DefaultRetryPolicy implements RetryPolicy {
    /**
     * Request当前超时时间
     */
    private int mCurrentTimeoutMs;

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

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

    /**
     * Request超时时间乘积因子
     */
    private final float mBackoffMultiplier;

    /**
     * Volley默认的超时时间(2.5s)
     */
    public static final int DEFAULT_TIMEOUT_MS = 2500;

    /**
     * Volley默认的重试次数(0次,不进行请求重试)
     */
    public static final int DEFAULT_MAX_RETRIES = 0;

    /**
     * 默认超时时间的乘积因子.
     * 以默认超时时间为2.5s为例:
     * 1. DEFAULT_BACKOFF_MULT = 1f, 则每次HttpUrlConnection设置的超时时间都是2.5s*1f*mCurrentRetryCount.
     * 2. 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;

    /**
     * Request的默认重试策略构造函数
     * 超时时间:2500ms
     * 重试次数:0次
     * 超时时间因子:1f
     */
    public DefaultRetryPolicy() {
        this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
    }

    /**
     * 开发者自定制Request重试策略构造函数
     *
     * @param initialTimeoutMs  超时时间
     * @param maxNumRetries     最大重试次数
     * @param backoffMultiplier 超时时间乘积因子
     */
    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;
        // 判断是否还有剩余次数,如果没有,则抛出VolleyError异常
        if (!hasAttemptRemaining()) {
            throw error;
        }
    }

    /**
     * 判断当前Request的重试次数是否超过最大重试次数
     */
    private boolean hasAttemptRemaining() {
        return mCurrentTimeoutMs <= mMaxNumRetries;
    }
}

本文开始时就讲过Request如何设置自定制的RetryPolicy,结合中文注释的DefaultRetryPolicy源码,相信大家很容易就能理解自定制RetryPolicy的参数含义和作用.

目前可能大家还有一个疑问,为什么DefaultRetryPolicy的retry方法抛出VolleyError异常,就能退出BasicNetwork类performRequest的while(true)循环呢?

这是因为BasicNetwork并没有捕获VolleyError异常,因此没有被try&catch住的异常会终止当前程序的运行,继续往外抛出,这时候就回到NetworkDispatcher类,相关源码如下:

@Override
public void run() {
    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    while (true) {
        long startTimeMs = SystemClock.elapsedRealtime();
        Request<?> request;
        try {
            // 使用BlockingQueue实现了生产者-消费者模型.
            // 消费者是该调度线程.
            // 生产者是request网络请求.
            request = mQueue.take();
        } catch (InterruptedException e) {
            // We may have been interrupted because it was time to quit.
            if (mQuit) {
                return;
            }
            continue;
        }

        try {
            if (request.isCanceled()) {
                continue;
            }

            addTrafficStatsTag(request);

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

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

从上述源码中可以看出,Request无法继续重试后抛出的VolleyError异常,会被NetworkDispatcher捕获,然后通过Delivery去回调用户设置的ErrorListener.


小结

至此,Volley的超时重试机制就分析完了,在本文末尾给大家推荐一下Volley默认重试策略的参数.

默认超时时间:Volley的默认2500ms确实有点短,大家可以设置成HttpClient的默认超时时间,也就是10000ms.

默认重试次数:建议为3次,可根据业务自行调整.

默认超时时间因子:建议采用DefaultRetryPolicy的默认值1f即可,不然曲线增长太快会造成页面长时间的等待.

更详细的Volley源码分析,大家可以参考我的GitHub项目:Volley源码分析

时间: 2024-10-25 23:53:00

Volley超时重试机制详解的相关文章

Hibernate 所有缓存机制详解

Hibernate 所有缓存机制详解 hibernate提供的一级缓存 hibernate是一个线程对应一个session,一个线程可以看成一个用户.也就是说session级缓存(一级缓存)只能给一个线程用,别的线程用不了,一级缓存就是和线程绑定了. hibernate一级缓存生命周期很短,和session生命周期一样,一级缓存也称session级的缓存或事务级缓存.如果tb事务提交或回滚了,我们称session就关闭了,生命周期结束了. 缓存和连接池的区别:缓存和池都是放在内存里,实现是一样的

心跳机制详解

应用场景: 在长连接下,有可能很长一段时间都没有数据往来.理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的.更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉.在这个时候,就需要我们的心跳包了,用于维持长连接,保活 什么是心跳机制? 就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息则视客户端断开. 发包方:可以是客户也可以是服务端,看哪边实现方便合理. 心跳包之所以叫心跳包是因

浏览器 HTTP 协议缓存机制详解

1.缓存的分类 2.浏览器缓存机制详解 2.1 HTML Meta标签控制缓存 2.2 HTTP头信息控制缓存 2.2.1 浏览器请求流程 2.2.2 几个重要概念解释 3.用户行为与缓存 4.Refer: https://www.cnblogs.com/520yang/articles/4807408.html 最近在准备优化日志请求时遇到了一些令人疑惑的问题,比如为什么响应头里出现了两个 cache control.为什么明明设置了 no cache 却还是发请求,为什么多次访问时有时请求里

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

【Hibernate步步为营】--锁机制详解

上篇文章详细讨论了hql的各种查询方法,在讨论过程中写了代码示例,hql的查询方法类似于sql,查询的方法比较简单,有sql基础的开发人员在使用hql时就会变得相当的简单.Hibernate在操作数据库的同时也提供了对数据库操作的限制方法,这种方法被称为锁机制,Hibernate提供的锁分为两种一种是乐观锁,另外一种是悲观锁.通过使用锁能够控制数据库的并发性操作,限制用户对数据库的并发性的操作. 一.锁简介 锁能控制数据库的并发操作,通过使用锁来控制数据库的并发操作,Hibernate提供了两种

浏览器缓存机制详解

对于浏览器缓存,相信很多开发者对它真的是又爱又恨.一方面极大地提升了用户体验,而另一方面有时会因为读取了缓存而展示了"错误"的东西,而在开发过程中千方百计地想把缓存禁掉.那么浏览器缓存究竟是个什么样的神奇玩意呢? 什么是浏览器缓存: 简单来说,浏览器缓存就是把一个已经请求过的Web资源(如html页面,图片,js,数据等)拷贝一份副本储存在浏览器中.缓存会根据进来的请求保存输出内容的副本.当下一个请求来到的时候,如果是相同的URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是

Android触摸屏事件派发机制详解与源码分析

请看下面三篇博客,思路还是蛮清晰的,不过还是没写自定义控件系列哥们的思路清晰: Android触摸屏事件派发机制详解与源码分析一(View篇) http://blog.csdn.net/yanbober/article/details/45887547 Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇) http://blog.csdn.net/yanbober/article/details/45912661 Android触摸屏事件派发机制详解与源码分析三(Activi

SpringMVC视图机制详解[附带源码分析]

目录 前言 重要接口和类介绍 源码分析 编码自定义的ViewResolver 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html 本文将分析SpringMVC的视图这部分内容,让读者了解SpringMVC视图的设计原理. 重要接口和类介绍 1. View接口 视图基础接口,它的各种实现类是无

Shiro的Filter机制详解---源码分析

Shiro的Filter机制详解 首先从spring-shiro.xml的filter配置说起,先回答两个问题: 1, 为什么相同url规则,后面定义的会覆盖前面定义的(执行的时候只执行最后一个). 2, 为什么两个url规则都可以匹配同一个url,只执行第一个呢. 下面分别从这两个问题入手,最终阅读源码得到解答. 问题一解答 相同url但定义在不同的行,后面覆盖前面 如 /usr/login.do=test3 /usr/login.do=test1,test2 不会执行test3的filter