今天在提交数据到后台接口时遇到一个奇怪问题,服务端同学通过后台log发现偶尔会打印两次一模一样的请求,时间间隔一般只有几秒钟。刚开始我感觉应该是在用户点击完Button之后没有把Button的状态置为false,在等待服务端返回数据期间用户又点击了一次Button,后来查看代码,发现在onClick()里已经调用了setEnabled(false),并且在收到服务器响应后就会立即跳转Fragment,所以个人感觉不存在多次点击Button的情况。为了定位问题,在onClick()里生成了一个唯一的UUID,当做请求的参数带上,问题复现后,查看服务端log发现接收到的UUID竟然是一样的,这就验证了自己的想法,即:多次请求并不是多次点击Button导致的。那么就只有一个可能了,就是一个请求被发送了两次。我们用的网络请求库是Volley,为了追踪问题,想起了那句经典的RTFSC(read the fucking source code)。
Volley的网络请求都是调用的BasicNetwork的performRequest()方法获取服务器响应。
|
|
代码有点长,但文档注释很详细,逻辑很清晰。大致的过程为:添加header到request–>发送http–>解析response–>返回。这个请求是循环执行的,直到收到服务端响应,或者抛出异常。在异常处理中,看到有一个attemptRetryOnException()方法,突然想起了以前阅读Volley源码时依稀记得Volley默认有重试的机制,顿时感觉豁然开朗,赶紧看一下这个方法。
方法很短,从Request中取出RetryPolicy对象,然后调用它的retry()方法,如果该方法抛出异常,performRequest()方法会终止执行且向上抛出异常,如果retry()没抛异常,则performRequest()方法继续while(true)的死循环,所以问题的关键就是分析RetryPolicy的retry()方法了。那这个RetryPolicy是什么时候设置到Request的呢?看一下setRetryPolicy()的调用。
|
|
在Request的构造里设置了一个DefaultRetryPolicy对象
那么看一下它的retry()方法。
关键逻辑hasAttempRemaining()方法,先把当前请求次数mCurrentRetryCount++,然后和设置的最大请求次数mMaxNumRetries比较,意思是当前尝试请求的次数大于最大请求数就抛出异常。而最大请求次数的默认值DEFAULT_MAX_RETRIES = 1。
到这里,一切就清晰了。由于Volley默认设置的超时时间比较短,只有2.5秒,这在天朝这种复杂的网络环境下简直是不可想象的,请求发送的数据量稍微大一点,或者2G、移动3G这种龟速网下,或者服务器处理速度慢一点,很容易发生TimeOut。当请求发出后,2.5秒没收到服务器的响应(有可能服务器已经收到了请求,在做逻辑处理,还没返回结果),则客户端抛出TimeOut异常,失败一次后,进入DefaultRetryPolicy的retry(),判断当前请求的次数(1) <= 默认的最大请求次数(1),所以不抛出异常,回到BasicNetwork的performRequest()方法继续执行while(true)循环,即再发一次request请求。
知道了原因,修复起来就很简单了,增加默认的超时时间或者设置本次提交数据的请求不再使用重试策略。
|
|