Android Volley 超时重试机制

喜欢ヅ旅行 2022-05-18 00:51 291阅读 0赞

前言:

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

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

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

  1. /** * Retry policy for a request. * * 用途: * 1. 重试策略,一定时间,重新发起一个请求。 * 2. 获取当前时间,当前重试的请求个数 */
  2. public interface RetryPolicy {
  3. /** * 获取当前重试的时间. */
  4. public int getCurrentTimeout();
  5. /** * 返回当前重试次数 */
  6. public int getCurrentRetryCount();
  7. /** * 当应用超时的时候,准备下一次的重试 * @param error 上一次尝试发生的异常 * @throws VolleyError 当尝试不能执行,会抛出异常 */
  8. public void retry(VolleyError error) throws VolleyError;
  9. }

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

  1. /** * Default retry policy for requests. * 用途: * 请求中默认的重试策略 * 时间,重试次数,回退的乘数 */
  2. public class DefaultRetryPolicy implements RetryPolicy {
  3. /** 当前超时累计的时间(毫秒为单位) */
  4. private int mCurrentTimeoutMs;
  5. /** 当前重试次数 */
  6. private int mCurrentRetryCount;
  7. /** 最大重试次数 */
  8. private final int mMaxNumRetries;
  9. /** 超时的乘数因子 */
  10. private final float mBackoffMultiplier;
  11. /** 默认的重试一次的时间*/
  12. public static final int DEFAULT_TIMEOUT_MS = 2500;
  13. /** 默认的重试次数 */
  14. public static final int DEFAULT_MAX_RETRIES = 1;
  15. /** * 默认的超时的乘数因子 * * 当前的重试时间=上一次超时时间+(上一次的超时时间*乘数因子) * * 例如: 乘数因子为1 ,一次重试时间为2.5秒 ,最大的重试次数为2 * 第一次重试: 重试时间=2.5+2.5*1=5秒 * 第二次重试: 重试时间=5+5*1=10秒 * */
  16. public static final float DEFAULT_BACKOFF_MULT = 1f;
  17. /** * 使用默认的重试策略 */
  18. public DefaultRetryPolicy() {
  19. this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
  20. }
  21. /** * * * 参数1:策略执行时间 * 参数2:在执行时间内,重试次数 * 参数3:回退的乘数。 当前时间+=(当前时间*参数3) * */
  22. public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
  23. mCurrentTimeoutMs = initialTimeoutMs;
  24. mMaxNumRetries = maxNumRetries;
  25. mBackoffMultiplier = backoffMultiplier;
  26. }
  27. /** * 返回当前重试的累加时间. */
  28. @Override
  29. public int getCurrentTimeout() {
  30. return mCurrentTimeoutMs;
  31. }
  32. /** * 返回当前的重试次数,第几次重试 */
  33. @Override
  34. public int getCurrentRetryCount() {
  35. return mCurrentRetryCount;
  36. }
  37. /** * 重试策略执行逻辑 */
  38. @Override
  39. public void retry(VolleyError error) throws VolleyError {
  40. //累加尝试次数
  41. mCurrentRetryCount++;
  42. //累加重试时间
  43. mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
  44. if (!hasAttemptRemaining()) {
  45. //当前的重试次数大于指定重试次数,抛出该异常
  46. throw error;
  47. }
  48. }
  49. /** * 返回true ,则请求还有重试次数 */
  50. protected boolean hasAttemptRemaining() {
  51. return mCurrentRetryCount <= mMaxNumRetries;
  52. }
  53. }

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

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

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

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

  1. public class HurlStack implements HttpStack {
  2. //...省略部分代码
  3. /** * 根据url中带有的协议,来开启一个带有参数的HttpURLConnection,或者HttpsURLConnection */
  4. private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
  5. HttpURLConnection connection = createConnection(url);
  6. //获取Request中的指定时间
  7. int timeoutMs = request.getTimeoutMs();
  8. //设置连接时间
  9. connection.setConnectTimeout(timeoutMs);
  10. //设置读取时间
  11. connection.setReadTimeout(timeoutMs);
  12. //...省略部分代码
  13. return connection;
  14. }
  15. }

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

接下来,找到Request类:

  1. public abstract class Request<T> implements Comparable<Request<T>> {
  2. public Request(int method, String url, Response.ErrorListener listener) {
  3. //....省略部分代码
  4. setRetryPolicy(new DefaultRetryPolicy());
  5. }
  6. /** * 设置重试策略 */
  7. public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
  8. mRetryPolicy = retryPolicy;
  9. return this;
  10. }
  11. /** * 重试策略中的当前累加重试时间 */
  12. public final int getTimeoutMs() {
  13. return mRetryPolicy.getCurrentTimeout();
  14. }
  15. }

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

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

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

  1. public class BasicNetwork implements Network {
  2. /** * 执行网络请求,返回响应数据 * * @param request Request to process * @return * @throws VolleyError 执行网络请求,for循环的方式,执行重试策略。 * <p> * 若是执行成功或者重试策略执行完抛出异常,跳出for循环。 */
  3. @Override
  4. public NetworkResponse performRequest(Request<?> request) throws VolleyError {
  5. //引导后的毫秒数(包含睡眠花费的时间)
  6. long requestStart = SystemClock.elapsedRealtime();
  7. while (true) {
  8. //执行Http请求,每次循环都执行最新的重试时间
  9. mHttpStack.performRequest(request, headers);
  10. //若是服务器返回状态码在小于200或者待遇299时,抛出一个异常
  11. if (statusCode < 200 || statusCode > 299) {
  12. throw new IOException();
  13. }
  14. return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
  15. } catch (SocketTimeoutException e) {
  16. //执行重试策略
  17. attemptRetryOnException("socket", request, new TimeoutError());
  18. } catch (ConnectTimeoutException e) {
  19. //执行重试策略
  20. attemptRetryOnException("connection", request, new TimeoutError());
  21. } catch (IOException e) {
  22. int statusCode = 0;
  23. NetworkResponse networkResponse = null;
  24. if (httpResponse != null) {
  25. statusCode = httpResponse.getStatusLine().getStatusCode();
  26. } else {
  27. throw new NoConnectionError(e);
  28. }
  29. VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
  30. if (responseContents != null) {
  31. networkResponse = new NetworkResponse(statusCode, responseContents, responseHeaders, false);
  32. if (statusCode == HttpStatus.SC_UNAUTHORIZED || statusCode == HttpStatus.SC_FORBIDDEN) {
  33. //执行重试策略
  34. attemptRetryOnException("auth", request, new AuthFailureError(networkResponse));
  35. } else {
  36. // TODO: Only throw ServerError for 5xx status codes.
  37. throw new ServerError(networkResponse);
  38. }
  39. } else {
  40. throw new NetworkError(networkResponse);
  41. }
  42. }
  43. }
  44. }
  45. /** * 执行,重试策略。 * <p> * 若是请求中已经没有更多的重试策略,抛出这次请求网络的异常。 * * @param request The request to use. */
  46. private static void attemptRetryOnException(String logPrefix, Request<?> request, VolleyError exception) throws VolleyError {
  47. RetryPolicy retryPolicy = request.getRetryPolicy();
  48. //上一次重试后的累计的时间
  49. int oldTimeout = request.getTimeoutMs();
  50. try {
  51. retryPolicy.retry(exception);
  52. } catch (VolleyError e) {
  53. request.addMarker(String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
  54. throw e;
  55. }
  56. request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
  57. }
  58. }

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

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

找到 NetworkDispatcher类:

  1. public class NetworkDispatcher extends Thread {
  2. @Override
  3. public void run() {
  4. //设置线程优先级,这里是后台线程
  5. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  6. Request<?> request;
  7. //while循环,从网络队列中获取要执行的请求。
  8. while (true) {
  9. try {
  10. // 从网络队列中获取一个请求
  11. request = mQueue.take();
  12. } catch (InterruptedException e) {
  13. //当队列中抛出一个异常,且程序需要关闭网络线程池,则停止该线程。
  14. if (mQuit) {
  15. return;
  16. }
  17. continue;
  18. }
  19. try {
  20. //....省略部分代码
  21. //在NetWork子类类中执行网络请求的操作,返回网络响应数据
  22. NetworkResponse networkResponse = mNetwork.performRequest(request);
  23. //....省略部分代码
  24. //在网络线程中指向解析响应的数据
  25. Response<?> response = request.parseNetworkResponse(networkResponse);
  26. //....省略部分代码
  27. //在ResponseDelivery类中回调请求和解析后响应数据
  28. mDelivery.postResponse(request, response);
  29. } catch (VolleyError volleyError) {
  30. parseAndDeliverNetworkError(request, volleyError);
  31. } catch (Exception e) {
  32. VolleyLog.e(e, "Unhandled exception %s", e.toString());
  33. mDelivery.postError(request, new VolleyError(e));
  34. }
  35. }
  36. }
  37. /** * 解析,传递网络异常。 * @param request * @param error */
  38. private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
  39. error = request.parseNetworkError(error);
  40. mDelivery.postError(request, error);
  41. }
  42. }

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

发表评论

表情:
评论列表 (有 0 条评论,291人围观)

还没有评论,来说两句吧...

相关阅读

    相关 超时机制

    网上发现这篇好文章,这里记录学习。 介绍       在实际开发过程中,笔者见过太多故障是因为超时没有设置或者设置的不对而造成的。而这些故障都是因为没有意识到超时设置的