承上
二 拦截器
拦截器的作用主要是为了统一做一些事情,避免重复,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强大的日志功能。