Retrofit在网络层完全依赖于OkHttp,有人为它做了一个日志拦截器来输出日志,首先在gradle中添加依赖:
compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
为OkHttp添加拦截器
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();// set your desired log levellogging.setLevel(Level.BODY);OkHttpClient.Builder httpClient = new OkHttpClient.Builder();// add your other interceptors …// add logging as last interceptorhttpClient.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.1D/HttpLoggingInterceptor$Logger: Accept: application/jsonD/HttpLoggingInterceptor$Logger: Content-Type: application/jsonD/HttpLoggingInterceptor$Logger: --> END POSTD/HttpLoggingInterceptor$Logger: <-- HTTP/1.1 200 OK (1039ms)D/HttpLoggingInterceptor$Logger: content-type: text/html; charset=utf-8D/HttpLoggingInterceptor$Logger: cache-control: no-cacheD/HttpLoggingInterceptor$Logger: vary: accept-encodingD/HttpLoggingInterceptor$Logger: Date: Wed, 28 Oct 2015 08:24:20 GMTD/HttpLoggingInterceptor$Logger: Connection: keep-aliveD/HttpLoggingInterceptor$Logger: Transfer-Encoding: chunkedD/HttpLoggingInterceptor$Logger: OkHttp-Selected-Protocol: http/1.1D/HttpLoggingInterceptor$Logger: OkHttp-Sent-Millis: 1446020610352D/HttpLoggingInterceptor$Logger: OkHttp-Received-Millis: 1446020610369D/HttpLoggingInterceptor$Logger: <-- END HTTP
除了服务器响应的头信息,还会输出选择协议的信息以及发送请求和接收响应时的相应毫秒数。
Body
输出请求和响应的头信息(headers)和内容(body)。
这是您将获得响应正文数据的唯一日志级别。 仅在必要时使用此级别。
D/HttpLoggingInterceptor$Logger: --> POST /upload HTTP/1.1D/HttpLoggingInterceptor$Logger: --9df820bb-bc7e-4a93-bb67-5f28f4140795D/HttpLoggingInterceptor$Logger: Content-Disposition: form-data; name="description"D/HttpLoggingInterceptor$Logger: Content-Transfer-Encoding: binaryD/HttpLoggingInterceptor$Logger: Content-Type: application/json; charset=UTF-8D/HttpLoggingInterceptor$Logger: Content-Length: 37D/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-8D/HttpLoggingInterceptor$Logger: cache-control: no-cacheD/HttpLoggingInterceptor$Logger: vary: accept-encodingD/HttpLoggingInterceptor$Logger: Date: Wed, 28 Oct 2015 08:33:40 GMTD/HttpLoggingInterceptor$Logger: Connection: keep-aliveD/HttpLoggingInterceptor$Logger: Transfer-Encoding: chunkedD/HttpLoggingInterceptor$Logger: OkHttp-Selected-Protocol: http/1.1D/HttpLoggingInterceptor$Logger: OkHttp-Sent-Millis: 1446021170095D/HttpLoggingInterceptor$Logger: OkHttp-Received-Millis: 1446021170107D/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 buildlogging.setLevel(Level.BODY);} else {// production buildlogging.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>() {@Overridepublic 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());}}@Overridepublic void onFailure(Call<User> call, Throwable t) {// there is more than just a failing request (like: no internet connection)}});
这样就可以使用ErrorUtils类来处理错误了。