retrofit框架探究(三)

54 篇文章 0 订阅
20 篇文章 0 订阅


承上


二 拦截器

拦截器的作用主要是为了统一做一些事情,避免重复,retrofit的网络拦截器在版本1只提供了为数不多的几个方法,这几个方法都定义在一个叫RequestFacade的接口中,我们在自定义拦截器的时候获取到的就是它。实现一个拦截器方法如下:

public class MarvelSigningInterceptor implements RequestInterceptor {

  public MarvelSigningInterceptor() {
  }

  @Override public void intercept(RequestFacade request) {
  }
}

那么关于RequestFacade和RequestInterceptor以及RequestInterceptorTape三个类有必要进行了解:

RequestFacade和RequestInterceptor

/** Intercept every request before it is executed in order to add additional data. */
public interface RequestInterceptor {
  /** Called for every request. Add data using methods on the supplied {@link RequestFacade}. */
  void intercept(RequestFacade request);

  interface RequestFacade {
    /** Add a header to the request. This will not replace any existing headers. */
    void addHeader(String name, String value);

    /**
     * Add a path parameter replacement. This works exactly like a {@link retrofit.http.Path
     * @Path}-annotated method argument.
     */
    void addPathParam(String name, String value);

    /**
     * Add a path parameter replacement without first URI encoding. This works exactly like a
     * {@link retrofit.http.Path @Path}-annotated method argument with {@code encode=false}.
     */
    void addEncodedPathParam(String name, String value);

    /** Add an additional query parameter. This will not replace any existing query parameters. */
    void addQueryParam(String name, String value);

    /**
     * Add an additional query parameter without first URI encoding. This will not replace any
     * existing query parameters.
     */
    void addEncodedQueryParam(String name, String value);
  }

  /** A {@link RequestInterceptor} which does no modification of requests. */
  RequestInterceptor NONE = new RequestInterceptor() {
    @Override public void intercept(RequestFacade request) {
      // Do nothing.
    }
  };
}

可以看到RequestFacade接口定义在RequestInterceptor接口内部,其注释上也说明了,它可以在每个接口请求执行之前添加一些额外的数据。RequestFacade内部定义了一些方法,主要针对header,传统参数,以及restful参数三个部分,这三个部分就是可以提供给开发者的添加参数的方法,当然它也有一个默认实现,即不作任何处理。另外一个相关类便是RequestInterceptor的实现类,RequestInterceptorTape:

/**
 * Records methods called against it as a RequestFacade and replays them when called as a
 * RequestInterceptor.
 */
final class RequestInterceptorTape implements RequestInterceptor.RequestFacade, RequestInterceptor {

  private final List<CommandWithParams> tape = new ArrayList<CommandWithParams>();

  @Override public void addHeader(String name, String value) {
    tape.add(new CommandWithParams(Command.ADD_HEADER, name, value));
  }

  @Override public void addPathParam(String name, String value) {
    tape.add(new CommandWithParams(Command.ADD_PATH_PARAM, name, value));
  }

  @Override public void addEncodedPathParam(String name, String value) {
    tape.add(new CommandWithParams(Command.ADD_ENCODED_PATH_PARAM, name, value));
  }

  @Override public void addQueryParam(String name, String value) {
    tape.add(new CommandWithParams(Command.ADD_QUERY_PARAM, name, value));
  }

  @Override public void addEncodedQueryParam(String name, String value) {
    tape.add(new CommandWithParams(Command.ADD_ENCODED_QUERY_PARAM, name, value));
  }

  @Override public void intercept(RequestFacade request) {
    for (CommandWithParams cwp : tape) {
      cwp.command.intercept(request, cwp.name, cwp.value);
    }
  }

  private enum Command {
    ADD_HEADER {
      @Override
      public void intercept(RequestFacade facade, String name, String value) {
        facade.addHeader(name, value);
      }
    },
    ADD_PATH_PARAM {
      @Override
      public void intercept(RequestFacade facade, String name, String value) {
        facade.addPathParam(name, value);
      }
    },
    ADD_ENCODED_PATH_PARAM {
      @Override
      public void intercept(RequestFacade facade, String name, String value) {
        facade.addEncodedPathParam(name, value);
      }
    },
    ADD_QUERY_PARAM {
      @Override
      public void intercept(RequestFacade facade, String name, String value) {
        facade.addQueryParam(name, value);
      }
    },
    ADD_ENCODED_QUERY_PARAM {
      @Override
      public void intercept(RequestFacade facade, String name, String value) {
        facade.addEncodedQueryParam(name, value);
      }
    };

    abstract void intercept(RequestFacade facade, String name, String value);
  }

  private static final class CommandWithParams {
    final Command command;
    final String name;
    final String value;

    CommandWithParams(Command command, String name, String value) {
      this.command = command;
      this.name = name;
      this.value = value;
    }
  }
}

这里需要说明的是,同步请求与该类无缘,只有异步请求才会有它的身影。首先对这个类的定位,它的内部有一个集合,那么可以断定它有存储功能,然后它有针对该集合进行添加的方法,我们可以看作是输入,那么其intercept方法便是输出,因为该方法传入了一个RequestFacade对象,作了遍历依次将当前类的集合内的数据根据不同的command转移到传入的RequestFacade。这种写法很巧妙,巧妙的地方在于这个类很像渡船,船头负责在西岸上货,船尾在东岸卸货,而这样的渡船可以在上货之后可以直接卸货给下一个渡船,然后下一个渡船可以接着送货。那么在我们的例子里,我们传入了MarvelSigningInterceptor作为拦截器,在invoke方法内,它使用我们的拦截器调用intercept方法,传入的是RequestInterceptorTape对象,拿到这个对象后假如我们添加了一个token的键值对到RequestInterceptorTape里,它拿到我们的键值对,在invokeRequest方法里以一模一样方式调用RequestInterceptorTape的intercept方法,传入的参数是RequestBuilder对象,然后它将自己的所有货物转手给了RequestBuilder对象,而我们请求的最终形态Request对象就是由它创建的。


三 异常RetrofitError

在invokeRequest方法里,请求的执行就不多说了,但对异常的灵活运用也是一道风景。

try {
        

        ......


        if (statusCode >= 200 && statusCode < 300) { // 2XX == successful request
          // Caller requested the raw Response object directly.
          
          ......

          ExceptionCatchingTypedInput wrapped = new ExceptionCatchingTypedInput(body);
          try {
            Object convert = converter.fromBody(wrapped, type);
            logResponseBody(body, convert);
            if (methodInfo.isSynchronous) {
              return convert;
            }
            return new ResponseWrapper(response, convert);
          } catch (ConversionException e) {
            // If the underlying input stream threw an exception, propagate that rather than
            // indicating that it was a conversion exception.
            if (wrapped.threwException()) {
              throw wrapped.getThrownException();
            }

            // The response body was partially read by the converter. Replace it with null.
            response = Utils.replaceResponseBody(response, null);

            throw RetrofitError.conversionError(url, response, converter, type, e);
          }
        }

        response = Utils.readBodyToBytesIfNecessary(response);
        throw RetrofitError.httpError(url, response, converter, type);
      } catch (RetrofitError e) {
        throw e; // Pass through our own errors.
      } catch (IOException e) {
        if (logLevel.log()) {
          logException(e, url);
        }
        throw RetrofitError.networkError(url, e);
      } catch (Throwable t) {
        if (logLevel.log()) {
          logException(t, url);
        }
        throw RetrofitError.unexpectedError(url, t);
      } finally {
        if (!methodInfo.isSynchronous) {
          Thread.currentThread().setName(IDLE_THREAD_NAME);
        }
      }

上面的代码可以看出,在数据转换时发生的转换异常会被转化为RetrofitError抛出,如果是请求异常,也会被转化为RetrofitError抛出,还有IO异常以及其他未知的异常,基本上都会被转化为RetrofitError,而在RetrofitError内部对异常的定义也是作了区分的,也就是上面这几种,转换异常,http异常,网络异常以及未知异常。

/*
 * Copyright (C) 2012 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package retrofit;

import java.io.IOException;
import java.lang.reflect.Type;
import retrofit.client.Response;
import retrofit.converter.ConversionException;
import retrofit.converter.Converter;
import retrofit.mime.TypedInput;

public class RetrofitError extends RuntimeException {
  public static RetrofitError networkError(String url, IOException exception) {
    return new RetrofitError(exception.getMessage(), url, null, null, null, Kind.NETWORK,
        exception);
  }

  public static RetrofitError conversionError(String url, Response response, Converter converter,
      Type successType, ConversionException exception) {
    return new RetrofitError(exception.getMessage(), url, response, converter, successType,
        Kind.CONVERSION, exception);
  }

  public static RetrofitError httpError(String url, Response response, Converter converter,
      Type successType) {
    String message = response.getStatus() + " " + response.getReason();
    return new RetrofitError(message, url, response, converter, successType, Kind.HTTP, null);
  }

  public static RetrofitError unexpectedError(String url, Throwable exception) {
    return new RetrofitError(exception.getMessage(), url, null, null, null, Kind.UNEXPECTED,
        exception);
  }

  /** Identifies the event kind which triggered a {@link RetrofitError}. */
  public enum Kind {
    /** An {@link IOException} occurred while communicating to the server. */
    NETWORK,
    /** An exception was thrown while (de)serializing a body. */
    CONVERSION,
    /** A non-200 HTTP status code was received from the server. */
    HTTP,
    /**
     * An internal error occurred while attempting to execute a request. It is best practice to
     * re-throw this exception so your application crashes.
     */
    UNEXPECTED
  }

  private final String url;
  private final Response response;
  private final Converter converter;
  private final Type successType;
  private final Kind kind;

  RetrofitError(String message, String url, Response response, Converter converter,
      Type successType, Kind kind, Throwable exception) {
    super(message, exception);
    this.url = url;
    this.response = response;
    this.converter = converter;
    this.successType = successType;
    this.kind = kind;
  }

  /** The request URL which produced the error. */
  public String getUrl() {
    return url;
  }

  /** Response object containing status code, headers, body, etc. */
  public Response getResponse() {
    return response;
  }

  /**
   * Whether or not this error was the result of a network error.
   *
   * @deprecated Use {@link #getKind() getKind() == Kind.NETWORK}.
   */
  @Deprecated public boolean isNetworkError() {
    return kind == Kind.NETWORK;
  }

  /** The event kind which triggered this error. */
  public Kind getKind() {
    return kind;
  }

  /**
   * HTTP response body converted to the type declared by either the interface method return type
   * or the generic type of the supplied {@link Callback} parameter. {@code null} if there is no
   * response.
   *
   * @throws RuntimeException if unable to convert the body to the {@link #getSuccessType() success
   * type}.
   */
  public Object getBody() {
    return getBodyAs(successType);
  }

  /**
   * The type declared by either the interface method return type or the generic type of the
   * supplied {@link Callback} parameter.
   */
  public Type getSuccessType() {
    return successType;
  }

  /**
   * HTTP response body converted to specified {@code type}. {@code null} if there is no response.
   *
   * @throws RuntimeException if unable to convert the body to the specified {@code type}.
   */
  public Object getBodyAs(Type type) {
    if (response == null) {
      return null;
    }
    TypedInput body = response.getBody();
    if (body == null) {
      return null;
    }
    try {
      return converter.fromBody(body, type);
    } catch (ConversionException e) {
      throw new RuntimeException(e);
    }
  }
}

其作用是显而易见的,如果是http异常,开发者可以拿到response,然后根据statuscode对用户作不同提示,如果是转换异常,开发者一般会希望看到转换异常的信息来辅助修改错误。

那么当把各种异常作了区分抛出后,就是对其进行集中捕获了,比如CallbackRunnable:

/**
 * A {@link Runnable} executed on a background thread to invoke {@link #obtainResponse()} which
 * performs an HTTP request. The response of the request, whether it be an object or exception, is
 * then marshaled to the supplied {@link Executor} in the form of a method call on a
 * {@link Callback}.
 */
abstract class CallbackRunnable<T> implements Runnable {
  private final Callback<T> callback;
  private final Executor callbackExecutor;
  private final ErrorHandler errorHandler;

  CallbackRunnable(Callback<T> callback, Executor callbackExecutor, ErrorHandler errorHandler) {
    this.callback = callback;
    this.callbackExecutor = callbackExecutor;
    this.errorHandler = errorHandler;
  }

  @SuppressWarnings("unchecked")
  @Override public final void run() {
    try {
      final ResponseWrapper wrapper = obtainResponse();
      callbackExecutor.execute(new Runnable() {
        @Override public void run() {
          callback.success((T) wrapper.responseBody, wrapper.response);
        }
      });
    } catch (RetrofitError e) {
      Throwable cause = errorHandler.handleError(e);
      final RetrofitError handled = cause == e ? e : unexpectedError(e.getUrl(), cause);
      callbackExecutor.execute(new Runnable() {
        @Override public void run() {
          callback.failure(handled);
        }
      });
    }
  }

  public abstract ResponseWrapper obtainResponse();
}


其中obtainResponse就是执行请求,如果期间有异常抛出,那么在这里就可以直接捕获到,紧接着利用调用请求失败的回调将异常信息传给开发者;如果期间没有异常抛出则执行成功的回调将response传递给开发者。这一套集中式的异常控制很好的规范了网络请求中的异常处理,因为通常情况下开发者最关心也最容易出错的地方就在这几个方面。而在回传给开发者前,实际上可以从上面的代码中找到,errorHandler.handleError(e)。这里的ErrorHandler类似于前面的interceptor,是个错误拦截器,这个拦截器允许我们自定义如何处理请求异常,在实际的开发当中,这一点尤为可贵。比如未能被捕获的异常在这里我们就可以作为日志上报给服务器,更重要的是登陆失效,属于http异常,只要返回code和我们定义好的失效code相等就可以立即跳转到登陆界面,如果登陆失效不集中处理,想象一下有多少地方需要做处理。

public final class ApiErrorHandler implements ErrorHandler {
  private static final int HTTP_UNPROCESSABLE_ENTITY = 422;

  private final Application app;

  public ApiErrorHandler(Application app) {
    this.app = app;
  }

  @Override public Throwable handleError(RetrofitError cause) {
    final RetrofitError.Kind kind = cause.getKind();
    final Response response = cause.getResponse();
    switch (kind) {
      case CONVERSION:
        break;
      case HTTP:
        if (response != null) {
          final int status = response.getStatus();
          if (status >= HttpURLConnection.HTTP_BAD_REQUEST && status != HTTP_UNPROCESSABLE_ENTITY) {
            Timber.e(cause, "Failed to load url: %s", response.getUrl());
          }
          if (status == HttpURLConnection.HTTP_UNAUTHORIZED) {
          // 认证失效
          }
        }
        break;
      case NETWORK:
        break;
      case UNEXPECTED:
        // 上报错误日志
        Timber.e(cause, "Failed to unknown error: %s" + cause.getMessage());
        break;
      default:
        break;
    }
    return cause;
  }
}


下一篇,继续学习retrofit强大的日志功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值