Retrofit 使用详解(五)

857 阅读4分钟
原文链接: rgf456.github.io
输出请求日志和响应日志

Retrofit在网络层完全依赖于OkHttp,有人为它做了一个日志拦截器来输出日志,首先在gradle中添加依赖:

compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'

为OkHttp添加拦截器

HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
// set your desired log level
logging.setLevel(Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
// add your other interceptors …
// add logging as last interceptor
httpClient.addInterceptor(logging); // <-- this is the important line!
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();

推荐把输出日志的拦截器放在其他拦截器的后面,这样就会打印出所有的内容。

Log Levels

OkHttp的日志输出有四个级别:

  • NONE
  • BASIC
  • HEADERS
  • BODY

None

没有日志输出。

Basic

日志会输出请求类型(request type),请求地址(url),请求大小(size of request body),响应码(response status)响应大小(size of response body)。

D/HttpLoggingInterceptor$Logger: --> POST /upload HTTP/1.1 (277-byte body)
D/HttpLoggingInterceptor$Logger: <-- HTTP/1.1 200 OK (543ms, -1-byte body)

Headers

输出请求和响应的头信息(headers),请求类型(request type),请求地址(request url),响应码(response status)。

使用HEADERS日志级别只会记录请求和响应的头信息。Retrofit或OkHttp会默认添加相应的请求头,但它们不会显示在您的请求上,因为它们稍后会添加到请求链中。 如果自己添加请求头,请确保日志拦截器是添加到OkHttp客户端的最后一个拦截器。 如果您第一个添加拦截器,请求上尚未设置任何头数据。

我们使用两个头字段Accept和Content-Type来说明输出:

D/HttpLoggingInterceptor$Logger: --> POST /upload HTTP/1.1
D/HttpLoggingInterceptor$Logger: Accept: application/json
D/HttpLoggingInterceptor$Logger: Content-Type: application/json
D/HttpLoggingInterceptor$Logger: --> END POST
D/HttpLoggingInterceptor$Logger: <-- HTTP/1.1 200 OK (1039ms)
D/HttpLoggingInterceptor$Logger: content-type: text/html; charset=utf-8
D/HttpLoggingInterceptor$Logger: cache-control: no-cache
D/HttpLoggingInterceptor$Logger: vary: accept-encoding
D/HttpLoggingInterceptor$Logger: Date: Wed, 28 Oct 2015 08:24:20 GMT
D/HttpLoggingInterceptor$Logger: Connection: keep-alive
D/HttpLoggingInterceptor$Logger: Transfer-Encoding: chunked
D/HttpLoggingInterceptor$Logger: OkHttp-Selected-Protocol: http/1.1
D/HttpLoggingInterceptor$Logger: OkHttp-Sent-Millis: 1446020610352
D/HttpLoggingInterceptor$Logger: OkHttp-Received-Millis: 1446020610369
D/HttpLoggingInterceptor$Logger: <-- END HTTP

除了服务器响应的头信息,还会输出选择协议的信息以及发送请求和接收响应时的相应毫秒数。

Body

输出请求和响应的头信息(headers)和内容(body)。

这是您将获得响应正文数据的唯一日志级别。 仅在必要时使用此级别。

D/HttpLoggingInterceptor$Logger: --> POST /upload HTTP/1.1
D/HttpLoggingInterceptor$Logger: --9df820bb-bc7e-4a93-bb67-5f28f4140795
D/HttpLoggingInterceptor$Logger: Content-Disposition: form-data; name="description"
D/HttpLoggingInterceptor$Logger: Content-Transfer-Encoding: binary
D/HttpLoggingInterceptor$Logger: Content-Type: application/json; charset=UTF-8
D/HttpLoggingInterceptor$Logger: Content-Length: 37
D/HttpLoggingInterceptor$Logger:
D/HttpLoggingInterceptor$Logger: "hello, this is description speaking"
D/HttpLoggingInterceptor$Logger: --9df820bb-bc7e-4a93-bb67-5f28f4140795--
D/HttpLoggingInterceptor$Logger: --> END POST (277-byte body)
D/HttpLoggingInterceptor$Logger: <-- HTTP/1.1 200 OK (1099ms)
D/HttpLoggingInterceptor$Logger: content-type: text/html; charset=utf-8
D/HttpLoggingInterceptor$Logger: cache-control: no-cache
D/HttpLoggingInterceptor$Logger: vary: accept-encoding
D/HttpLoggingInterceptor$Logger: Date: Wed, 28 Oct 2015 08:33:40 GMT
D/HttpLoggingInterceptor$Logger: Connection: keep-alive
D/HttpLoggingInterceptor$Logger: Transfer-Encoding: chunked
D/HttpLoggingInterceptor$Logger: OkHttp-Selected-Protocol: http/1.1
D/HttpLoggingInterceptor$Logger: OkHttp-Sent-Millis: 1446021170095
D/HttpLoggingInterceptor$Logger: OkHttp-Received-Millis: 1446021170107
D/HttpLoggingInterceptor$Logger: Perfect!
D/HttpLoggingInterceptor$Logger: <-- END HTTP (8-byte body)

只在开发环境下输出日志

自动化是增强开发人员关注点和生产力的最佳工具之一。启用和禁用Retrofit的日志记录可能是一个繁琐,重复的任务。所以让我们自动化这个过程:在开发过程中,将为调试版本启用日志记录; 并且您的应用程序的所有生产版本的日志记录将被禁用!

解决方案很简单:我们将使用由Android框架提供的BuildConfig.DEBUG布尔变量。 它将为您的开发环境返回true,对于您的生产环境返回false。

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(Level.BODY);
httpClient.addInterceptor(logging);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();

使用不同级别的日志

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
if (BuildConfig.DEBUG) {
// development build
logging.setLevel(Level.BODY);
} else {
// production build
logging.setLevel(Level.BASIC);
}
httpClient.addInterceptor(logging);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();

这样就可以在生产环境和正式环境中输出不同的日志了。

捕捉错误

在服务器发生错误或者用户输入的数据错误时,我们希望呈献给用户反馈信息,并要求他改正。

在这之前,我们来看一个简单的例子:

Error Object

首先我们创建一个错误json:

{
statusCode: 409,
message: "Email address already registered"
}

定义一个错误类

public class APIError {
private int statusCode;
private String message;
public APIError() {
}
public int status() {
return statusCode;
}
public String message() {
return message;
}
}

Simple Error Handler

我们将使用下面的类,返回一个APIError对象的静态方法。 parseError方法的参数为Response。 此外,您需要使您的Retrofit实例可用,以便为接收到的JSON错误响应应用适当的响应转换器。

public class ErrorUtils {
public static APIError parseError(Response<?> response) {
Converter<ResponseBody, APIError> converter =
ServiceGenerator.retrofit()
.responseBodyConverter(APIError.class, new Annotation[0]);
APIError error;
try {
error = converter.convert(response.errorBody());
} catch (IOException e) {
return new APIError();
}
return error;
}
}

首先将APIError类作为参数传递给responseBodyConverter方法,这个方法是retrofit中的方法。responseConverter方法将返回适当的转换器来解析响应体类型。

Error Handler inAction

在Retrofit 2中,所有可以执行(发送到API)并且接收到响应的请求都被视为“sucess”。这意味着,对于这些请求,onResponse回调被触发,您需要手动检查请求是否实际成功(状态200-299)或错误(状态400-599)。

如果请求成功完成,我们可以使用响应对象,并做任何我们想要的。 如果错误实际上失败(状态400-599),我们要向用户显示有关该问题的适当信息。

Call<User> call = service.me();
call.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
// use response data and do some fancy stuff :)
} else {
// parse the response body …
APIError error = ErrorUtils.parseError(response);
// … and use it to show error information
// … or just log the issue like we’re doing :)
Log.d("error message", error.message());
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
// there is more than just a failing request (like: no internet connection)
}
});

这样就可以使用ErrorUtils类来处理错误了。