Android Retrofit 框架日志与错误处理模块深度剖析(七)
一、引言
在 Android 开发中,网络请求是一项常见且重要的任务。Retrofit 作为一个强大的类型安全的 HTTP 客户端,被广泛应用于各种 Android 项目中。日志与错误处理模块在 Retrofit 框架中扮演着至关重要的角色,它们有助于开发者在开发和调试过程中监控网络请求的详细信息,以及在出现问题时快速定位和解决错误。本文将深入 Retrofit 框架的源码,详细分析其日志与错误处理模块的实现原理。
二、日志模块分析
2.1 日志模块概述
Retrofit 本身并不直接提供日志功能,而是借助 OkHttp 来实现日志记录。OkHttp 是一个高效的 HTTP 客户端,Retrofit 默认使用 OkHttp 作为底层的网络请求库。OkHttp 提供了一个 HttpLoggingInterceptor
拦截器,通过该拦截器可以方便地实现请求和响应的日志记录。
2.2 使用 HttpLoggingInterceptor
记录日志
以下是一个简单的示例,展示如何在 Retrofit 中使用 HttpLoggingInterceptor
记录日志:
java
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String baseUrl) {
// 创建 HttpLoggingInterceptor 实例
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
// 设置日志级别为 BODY,记录请求和响应的详细信息
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
// 创建 OkHttpClient 实例,并添加日志拦截器
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(logging);
// 创建 Retrofit 实例,并配置 OkHttpClient
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
return retrofit;
}
}
在上述代码中,我们创建了一个 HttpLoggingInterceptor
实例,并将其日志级别设置为 BODY
,表示记录请求和响应的详细信息。然后将该拦截器添加到 OkHttpClient
中,并使用该 OkHttpClient
来构建 Retrofit 实例。
2.3 HttpLoggingInterceptor
源码分析
2.3.1 日志级别枚举
java
public enum Level {
/** No logs. */
NONE,
/**
* Logs request and response lines.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1 (3-byte body)
*
* <-- 200 OK (22ms, 6-byte body)
* }</pre>
*/
BASIC,
/**
* Logs request and response lines and their respective headers.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }</pre>
*/
HEADERS,
/**
* Logs request and response lines and their respective headers and bodies (if present).
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
*
* Hi?
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
*
* Hello!
* <-- END HTTP
* }</pre>
*/
BODY
}
Level
枚举定义了不同的日志级别,包括 NONE
(不记录日志)、BASIC
(记录请求和响应的基本信息)、HEADERS
(记录请求和响应的基本信息以及头部信息)和 BODY
(记录请求和响应的详细信息,包括头部和主体)。
2.3.2 HttpLoggingInterceptor
核心逻辑
java
public final class HttpLoggingInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
public interface Logger {
// 日志记录方法
void log(String message);
/** A {@link Logger} defaults output appropriate for the current platform. */
Logger DEFAULT = new Logger() {
@Override public void log(String message) {
// 使用 Android 的 Log 类记录日志
android.util.Log.d("OkHttp", message);
}
};
}
private final Logger logger;
private volatile Level level = Level.NONE;
// 构造函数,传入日志记录器
public HttpLoggingInterceptor(Logger logger) {
this.logger = logger;
}
// 设置日志级别
public HttpLoggingInterceptor setLevel(Level level) {
if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
this.level = level;
return this;
}
// 获取当前日志级别
public Level getLevel() {
return level;
}
@Override public Response intercept(Chain chain) throws IOException {
// 获取当前日志级别
Level level = this.level;
Request request = chain.request();
if (level == Level.NONE) {
// 如果日志级别为 NONE,直接执行请求并返回响应
return chain.proceed(request);
}
boolean logBody = level == Level.BODY;
boolean logHeaders = logBody || level == Level.HEADERS;
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
Connection connection = chain.connection();
Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;
String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol;
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
}
// 记录请求开始信息
logger.log(requestStartMessage);
if (logHeaders) {
if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody.contentType() != null) {
// 记录请求体的 Content-Type 头部信息
logger.log("Content-Type: " + requestBody.contentType());
}
if (requestBody.contentLength() != -1) {
// 记录请求体的 Content-Length 头部信息
logger.log("Content-Length: " + requestBody.contentLength());
}
}
Headers headers = request.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
String name = headers.name(i);
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
// 记录请求的其他头部信息
logger.log(name + ": " + headers.value(i));
}
}
if (!logBody || !hasRequestBody) {
// 记录请求结束信息
logger.log("--> END " + request.method());
} else if (bodyEncoded(request.headers())) {
// 如果请求体是编码的,记录请求结束信息
logger.log("--> END " + request.method() + " (encoded body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (isPlaintext(buffer)) {
// 记录请求体的内容
logger.log(buffer.readString(charset));
// 记录请求结束信息
logger.log("--> END " + request.method() + " (" + requestBody.contentLength() + "-byte body)");
} else {
// 如果请求体不是纯文本,记录请求结束信息
logger.log("--> END " + request.method() + " (binary " + requestBody.contentLength() + "-byte body omitted)");
}
}
}
long startNs = System.nanoTime();
Response response;
try {
// 执行请求并获取响应
response = chain.proceed(request);
} catch (Exception e) {
// 记录请求异常信息
logger.log("<-- HTTP FAILED: " + e);
throw e;
}
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
// 记录响应开始信息
logger.log("<-- " + response.code() + ' ' + response.message() + ' '
+ response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')');
if (logHeaders) {
Headers headers = response.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
// 记录响应的头部信息
logger.log(headers.name(i) + ": " + headers.value(i));
}
if (!logBody || !HttpHeaders.hasBody(response)) {
// 记录响应结束信息
logger.log("<-- END HTTP");
} else if (bodyEncoded(response.headers())) {
// 如果响应体是编码的,记录响应结束信息
logger.log("<-- END HTTP (encoded body omitted)");
} else {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (!isPlaintext(buffer)) {
// 如果响应体不是纯文本,记录响应结束信息
logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
return response;
}
if (contentLength != 0) {
// 记录响应体的内容
logger.log(buffer.clone().readString(charset));
}
// 记录响应结束信息
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
return response;
}
// 判断请求或响应体是否是编码的
private boolean bodyEncoded(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
}
// 判断缓冲区的内容是否是纯文本
static boolean isPlaintext(Buffer buffer) {
try {
Buffer prefix = new Buffer();
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
buffer.copyTo(prefix, 0, byteCount);
for (int i = 0; i < 16; i++) {
if (prefix.exhausted()) {
break;
}
int codePoint = prefix.readUtf8CodePoint();
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false;
}
}
return true;
} catch (EOFException e) {
return false; // Truncated UTF-8 sequence.
}
}
}
HttpLoggingInterceptor
实现了 Interceptor
接口,其核心逻辑在 intercept
方法中。该方法会根据当前的日志级别记录请求和响应的详细信息,具体步骤如下:
-
请求信息记录:
- 记录请求的基本信息,如请求方法、URL 和协议。
- 如果日志级别为
HEADERS
或BODY
,记录请求的头部信息。 - 如果日志级别为
BODY
,且请求体是纯文本,记录请求体的内容。
-
执行请求:调用
chain.proceed(request)
方法执行请求,并获取响应。 -
响应信息记录:
- 记录响应的基本信息,如响应码、响应消息、URL 和请求耗时。
- 如果日志级别为
HEADERS
或BODY
,记录响应的头部信息。 - 如果日志级别为
BODY
,且响应体是纯文本,记录响应体的内容。
2.4 自定义日志记录器
除了使用默认的日志记录器,我们还可以自定义日志记录器。例如,将日志记录到文件中:
java
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class FileLogger implements HttpLoggingInterceptor.Logger {
private File logFile;
public FileLogger(File logFile) {
this.logFile = logFile;
}
@Override
public void log(String message) {
try {
FileWriter writer = new FileWriter(logFile, true);
writer.write(message + "\n");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
然后在创建 HttpLoggingInterceptor
实例时使用自定义的日志记录器:
java
File logFile = new File(context.getExternalFilesDir(null), "retrofit_log.txt");
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new FileLogger(logFile));
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
三、错误处理模块分析
3.1 错误处理概述
在 Retrofit 中,错误处理主要涉及两个方面:网络请求过程中的异常处理和服务器返回的错误响应处理。Retrofit 使用 Call
接口来表示一个网络请求,通过 enqueue
方法进行异步请求,通过 execute
方法进行同步请求。在请求过程中,可能会抛出各种异常,如网络连接异常、超时异常等,同时服务器也可能返回错误的响应码。
3.2 异步请求的错误处理
以下是一个异步请求的示例,展示了如何处理错误:
java
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class ApiService {
public interface MyApi {
@GET("data")
Call<Data> getData();
}
public void fetchData() {
Retrofit retrofit = RetrofitClient.getClient("https://api.example.com/");
MyApi api = retrofit.create(MyApi.class);
Call<Data> call = api.getData();
call.enqueue(new Callback<Data>() {
@Override
public void onResponse(Call<Data> call, Response<Data> response) {
if (response.isSuccessful()) {
// 请求成功,处理响应数据
Data data = response.body();
} else {
// 请求失败,处理错误响应
try {
String errorBody = response.errorBody().string();
// 处理错误信息
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Call<Data> call, Throwable t) {
// 请求过程中出现异常,处理异常
t.printStackTrace();
}
});
}
}
在上述代码中,enqueue
方法接受一个 Callback
对象,该对象包含 onResponse
和 onFailure
两个方法。onResponse
方法在请求完成后被调用,如果请求成功(响应码为 200 - 300),则可以通过 response.body()
获取响应数据;如果请求失败,则可以通过 response.errorBody()
获取错误响应体。onFailure
方法在请求过程中出现异常时被调用,如网络连接异常、超时异常等。
3.3 同步请求的错误处理
同步请求使用 execute
方法,需要在 try-catch
块中处理异常:
java
import retrofit2.Call;
import retrofit2.Response;
public class ApiService {
public interface MyApi {
@GET("data")
Call<Data> getData();
}
public void fetchData() {
Retrofit retrofit = RetrofitClient.getClient("https://api.example.com/");
MyApi api = retrofit.create(MyApi.class);
Call<Data> call = api.getData();
try {
Response<Data> response = call.execute();
if (response.isSuccessful()) {
// 请求成功,处理响应数据
Data data = response.body();
} else {
// 请求失败,处理错误响应
String errorBody = response.errorBody().string();
// 处理错误信息
}
} catch (IOException e) {
// 请求过程中出现异常,处理异常
e.printStackTrace();
}
}
}
在同步请求中,execute
方法会抛出 IOException
异常,因此需要在 try-catch
块中捕获并处理该异常。
3.4 Retrofit 错误处理源码分析
3.4.1 Call
接口
java
public interface Call<T> extends Cloneable {
// 同步执行请求
Response<T> execute() throws IOException;
// 异步执行请求
void enqueue(Callback<T> callback);
// 判断请求是否已经执行
boolean isExecuted();
// 取消请求
void cancel();
// 判断请求是否已经取消
boolean isCanceled();
// 克隆一个新的 Call 对象
Call<T> clone();
// 获取请求信息
Request request();
}
Call
接口定义了网络请求的基本操作,包括同步执行请求、异步执行请求、取消请求等。
3.4.2 OkHttpCall
类
OkHttpCall
是 Call
接口的具体实现类,负责实际的网络请求。以下是 enqueue
方法的源码:
java
final class OkHttpCall<T> implements Call<T> {
private final okhttp3.Call.Factory callFactory;
private final RequestFactory requestFactory;
private final Object[] args;
private final Converter<ResponseBody, T> responseConverter;
// 标记请求是否已经执行
private volatile boolean executed;
// 标记请求是否已经取消
private volatile boolean canceled;
// OkHttp 的 Call 对象
private okhttp3.Call rawCall;
@Override public void enqueue(final Callback<T> callback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
}
captureCallStackTrace();
okhttp3.Call call;
Throwable failure;
synchronized (this) {
if (canceled) {
failure = new IOException("Canceled");
call = null;
} else {
try {
// 创建 OkHttp 的 Call 对象
call = rawCall = createRawCall();
failure = null;
} catch (Throwable t) {
failure = t;
call = null;
}
}
}
if (failure != null) {
// 请求创建失败,调用 onFailure 方法
callback.onFailure(this, failure);
return;
}
if (canceled) {
// 请求已经取消,取消 OkHttp 的 Call 对象
call.cancel();
}
// 异步执行 OkHttp 的 Call 对象
call.enqueue(new okhttp3.Callback() {
@Override public void onFailure(okhttp3.Call call, IOException e) {
try {
// 请求过程中出现异常,调用 onFailure 方法
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
// 解析响应数据
response = parseResponse(rawResponse);
} catch (Throwable e) {
// 响应解析失败,调用 onFailure 方法
callFailure(e);
return;
}
try {
// 请求成功,调用 onResponse 方法
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
private okhttp3.Call createRawCall() throws IOException {
okhttp3.Request request = requestFactory.create(args);
// 创建 OkHttp 的 Call 对象
return callFactory.newCall(request);
}
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Remove the body's source (the only stateful object) so we can pass the response along.
rawResponse = rawResponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
int code = rawResponse.code();
if (code < 200 || code >= 300) {
try {
// 请求失败,解析错误响应体
ResponseBody bufferedBody = Utils.buffer(rawBody);
return Response.error(bufferedBody, rawResponse);
} finally {
rawBody.close();
}
}
if (code == 204 || code == 205) {
rawBody.close();
return Response.success(null, rawResponse);
}
ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
try {
// 请求成功,解析响应数据
T body = responseConverter.convert(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
// 响应解析失败,关闭响应体
catchingBody.throwIfCaught();
throw e;
}
}
}
enqueue
方法的主要步骤如下:
- 检查请求是否已经执行,如果已经执行则抛出异常。
- 创建 OkHttp 的
Call
对象,如果创建过程中出现异常,则调用callback.onFailure
方法。 - 异步执行 OkHttp 的
Call
对象,在onFailure
方法中处理请求过程中的异常,在onResponse
方法中解析响应数据。 - 在
parseResponse
方法中,根据响应码判断请求是否成功,如果请求失败,则解析错误响应体;如果请求成功,则解析响应数据。
3.4.3 Response
类
java
public final class Response<T> {
private final okhttp3.Response rawResponse;
private final T body;
private final ResponseBody errorBody;
// 构造函数
private Response(okhttp3.Response rawResponse, T body, ResponseBody errorBody) {
this.rawResponse = rawResponse;
this.body = body;
this.errorBody = errorBody;
}
// 判断请求是否成功
public boolean isSuccessful() {
return rawResponse.isSuccessful();
}
// 获取响应码
public int code() {
return rawResponse.code();
}
// 获取响应消息
public String message() {
return rawResponse.message();
}
// 获取响应头
public Headers headers() {
return rawResponse.headers();
}
// 获取响应数据
public T body() {
return body;
}
// 获取错误响应体
public ResponseBody errorBody() {
return errorBody;
}
// 创建成功响应
public static <T> Response<T> success(T body, okhttp3.Response rawResponse) {
if (rawResponse == null) throw new NullPointerException("rawResponse == null");
if (!rawResponse.isSuccessful()) {
throw new IllegalArgumentException("rawResponse must be successful response");
}
return new Response<>(rawResponse, body, null);
}
// 创建错误响应
public static <T> Response<T> error(ResponseBody body, okhttp3.Response rawResponse) {
if (rawResponse == null) throw new NullPointerException("rawResponse == null");
if (rawResponse.isSuccessful()) {
throw new IllegalArgumentException("rawResponse must not be successful response");
}
return new Response<>(rawResponse, null, body);
}
}
Response
类封装了响应的基本信息,包括响应码、响应消息、响应头、响应数据和错误响应体。通过 isSuccessful
方法可以判断请求是否成功,通过 body
方法可以获取响应数据,通过 errorBody
方法可以获取错误响应体。
3.5 全局错误处理
为了统一处理网络请求的错误,可以使用 Retrofit 的拦截器。以下是一个简单的全局错误处理拦截器示例:
java
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class ErrorHandlingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
try {
// 执行请求
Response response = chain.proceed(request);
if (!response.isSuccessful()) {
// 请求失败,处理错误响应
handleErrorResponse(response);
}
return response;
} catch (IOException e) {
//
3.5 全局错误处理
java
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class ErrorHandlingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
try {
// 执行请求
Response response = chain.proceed(request);
if (!response.isSuccessful()) {
// 请求失败,处理错误响应
handleErrorResponse(response);
}
return response;
} catch (IOException e) {
// 请求过程中出现异常,处理异常
handleNetworkException(e);
throw e;
}
}
private void handleErrorResponse(Response response) {
int code = response.code();
switch (code) {
case 400:
// 处理 400 Bad Request 错误
// 可以在这里记录日志、提示用户输入错误等
System.out.println("400 Bad Request: 可能是请求参数有误");
break;
case 401:
// 处理 401 Unauthorized 错误
// 可以在这里处理用户未授权的情况,如跳转到登录页面
System.out.println("401 Unauthorized: 用户未授权,请重新登录");
break;
case 403:
// 处理 403 Forbidden 错误
// 可以在这里处理权限不足的情况
System.out.println("403 Forbidden: 没有权限访问该资源");
break;
case 404:
// 处理 404 Not Found 错误
// 可以在这里提示用户请求的资源不存在
System.out.println("404 Not Found: 请求的资源不存在");
break;
case 500:
// 处理 500 Internal Server Error 错误
// 可以在这里提示用户服务器内部错误
System.out.println("500 Internal Server Error: 服务器内部出现错误");
break;
default:
// 处理其他错误
System.out.println("未知错误,错误码: " + code);
break;
}
}
private void handleNetworkException(IOException e) {
// 处理网络异常,如网络连接失败、超时等
if (e instanceof java.net.ConnectException) {
System.out.println("网络连接失败,请检查网络设置");
} else if (e instanceof java.net.SocketTimeoutException) {
System.out.println("请求超时,请稍后重试");
} else {
System.out.println("网络请求出现异常: " + e.getMessage());
}
}
}
在上述代码中,ErrorHandlingInterceptor
实现了 Interceptor
接口,在 intercept
方法中执行请求。如果请求失败(响应码不在 200 - 300 范围内),调用 handleErrorResponse
方法处理错误响应;如果请求过程中出现 IOException
异常,调用 handleNetworkException
方法处理网络异常。
要将这个拦截器应用到 Retrofit 中,可以在创建 OkHttpClient
时添加该拦截器:
java
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String baseUrl) {
// 创建错误处理拦截器
ErrorHandlingInterceptor errorInterceptor = new ErrorHandlingInterceptor();
// 创建 OkHttpClient 实例,并添加错误处理拦截器
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(errorInterceptor);
// 创建 Retrofit 实例,并配置 OkHttpClient
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
return retrofit;
}
}
3.6 自定义错误处理
除了使用拦截器进行全局错误处理,还可以通过自定义 CallAdapter
和 Converter
来实现更灵活的错误处理。
3.6.1 自定义 CallAdapter
java
import java.lang.reflect.Type;
import java.util.concurrent.Executor;
import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Retrofit;
public class ErrorHandlingCallAdapterFactory extends CallAdapter.Factory {
@Override
public CallAdapter<?, ?> get(Type returnType, java.lang.annotation.Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
final CallAdapter<Object, Call<Object>> delegate = (CallAdapter<Object, Call<Object>>) retrofit.nextCallAdapter(this, returnType, annotations);
return new CallAdapter<Object, Call<Object>>() {
@Override
public Type responseType() {
return delegate.responseType();
}
@Override
public Call<Object> adapt(Call<Object> call) {
return new ErrorHandlingCall<>(call);
}
};
}
private static class ErrorHandlingCall<T> implements Call<T> {
private final Call<T> delegate;
ErrorHandlingCall(Call<T> delegate) {
this.delegate = delegate;
}
@Override
public retrofit2.Response<T> execute() throws java.io.IOException {
try {
return delegate.execute();
} catch (java.io.IOException e) {
// 处理同步请求的异常
handleNetworkException(e);
throw e;
}
}
@Override
public void enqueue(final retrofit2.Callback<T> callback) {
delegate.enqueue(new retrofit2.Callback<T>() {
@Override
public void onResponse(Call<T> call, retrofit2.Response<T> response) {
if (!response.isSuccessful()) {
// 处理异步请求的错误响应
handleErrorResponse(response);
}
callback.onResponse(call, response);
}
@Override
public void onFailure(Call<T> call, Throwable t) {
if (t instanceof java.io.IOException) {
// 处理异步请求的网络异常
handleNetworkException((java.io.IOException) t);
}
callback.onFailure(call, t);
}
});
}
@Override
public boolean isExecuted() {
return delegate.isExecuted();
}
@Override
public void cancel() {
delegate.cancel();
}
@Override
public boolean isCanceled() {
return delegate.isCanceled();
}
@Override
public Call<T> clone() {
return new ErrorHandlingCall<>(delegate.clone());
}
@Override
public retrofit2.Request request() {
return delegate.request();
}
private void handleErrorResponse(retrofit2.Response<T> response) {
int code = response.code();
switch (code) {
case 400:
System.out.println("400 Bad Request: 可能是请求参数有误");
break;
case 401:
System.out.println("401 Unauthorized: 用户未授权,请重新登录");
break;
case 403:
System.out.println("403 Forbidden: 没有权限访问该资源");
break;
case 404:
System.out.println("404 Not Found: 请求的资源不存在");
break;
case 500:
System.out.println("500 Internal Server Error: 服务器内部出现错误");
break;
default:
System.out.println("未知错误,错误码: " + code);
break;
}
}
private void handleNetworkException(java.io.IOException e) {
if (e instanceof java.net.ConnectException) {
System.out.println("网络连接失败,请检查网络设置");
} else if (e instanceof java.net.SocketTimeoutException) {
System.out.println("请求超时,请稍后重试");
} else {
System.out.println("网络请求出现异常: " + e.getMessage());
}
}
}
}
在上述代码中,ErrorHandlingCallAdapterFactory
继承自 CallAdapter.Factory
,重写 get
方法返回一个自定义的 CallAdapter
。ErrorHandlingCall
是一个自定义的 Call
实现类,在 execute
和 enqueue
方法中处理请求的异常和错误响应。
要使用这个自定义的 CallAdapter
,可以在创建 Retrofit 实例时添加:
java
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addCallAdapterFactory(new ErrorHandlingCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build();
3.6.2 自定义 Converter
java
import java.io.IOException;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
public class ErrorHandlingConverterFactory extends Converter.Factory {
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, java.lang.annotation.Annotation[] annotations, Retrofit retrofit) {
final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return new Converter<ResponseBody, Object>() {
@Override
public Object convert(ResponseBody value) throws IOException {
try {
return delegate.convert(value);
} catch (IOException e) {
// 处理响应转换异常
handleConversionException(e);
throw e;
}
}
};
}
private void handleConversionException(IOException e) {
System.out.println("响应转换出现异常: " + e.getMessage());
}
}
在上述代码中,ErrorHandlingConverterFactory
继承自 Converter.Factory
,重写 responseBodyConverter
方法返回一个自定义的 Converter
。在 convert
方法中处理响应转换过程中出现的异常。
要使用这个自定义的 Converter
,可以在创建 Retrofit 实例时添加:
java
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(new ErrorHandlingConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build();
3.7 重试机制
在网络请求中,有时候请求失败可能是由于临时的网络问题导致的,这时可以实现一个重试机制来提高请求的成功率。以下是一个简单的重试拦截器示例:
java
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class RetryInterceptor implements Interceptor {
private int maxRetries;
public RetryInterceptor(int maxRetries) {
this.maxRetries = maxRetries;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
IOException exception = null;
for (int i = 0; i <= maxRetries; i++) {
try {
response = chain.proceed(request);
if (response.isSuccessful()) {
return response;
}
} catch (IOException e) {
exception = e;
}
}
if (exception != null) {
throw exception;
}
return response;
}
}
在上述代码中,RetryInterceptor
实现了 Interceptor
接口,在 intercept
方法中进行重试操作。最多重试 maxRetries
次,如果请求成功则返回响应,否则抛出最后一次出现的异常。
要将这个重试拦截器应用到 Retrofit 中,可以在创建 OkHttpClient
时添加该拦截器:
java
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String baseUrl) {
// 创建重试拦截器,最多重试 3 次
RetryInterceptor retryInterceptor = new RetryInterceptor(3);
// 创建 OkHttpClient 实例,并添加重试拦截器
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(retryInterceptor);
// 创建 Retrofit 实例,并配置 OkHttpClient
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
return retrofit;
}
}
四、总结
Retrofit 的日志与错误处理模块通过借助 OkHttp 的拦截器和自身的回调机制,为开发者提供了强大而灵活的日志记录和错误处理能力。
4.1 日志模块总结
- 日志记录方式:Retrofit 借助 OkHttp 的
HttpLoggingInterceptor
实现日志记录,通过设置不同的日志级别(NONE
、BASIC
、HEADERS
、BODY
)可以控制日志的详细程度。 - 自定义日志记录器:开发者可以自定义日志记录器,将日志记录到文件或其他存储介质中,满足不同的日志记录需求。
4.2 错误处理模块总结
-
异步和同步请求错误处理:在异步请求中,通过
Callback
接口的onResponse
和onFailure
方法处理请求的成功和失败;在同步请求中,通过try-catch
块捕获并处理IOException
异常。 -
全局错误处理:可以使用拦截器实现全局错误处理,统一处理不同类型的错误响应和网络异常,提高代码的可维护性。
-
自定义错误处理:通过自定义
CallAdapter
和Converter
可以实现更灵活的错误处理,例如在请求执行和响应转换过程中处理异常。 -
重试机制:实现重试拦截器可以在请求失败时进行重试,提高请求的成功率,特别是在网络不稳定的情况下。
通过深入理解和掌握 Retrofit 的日志与错误处理模块,开发者可以更好地监控网络请求的状态,快速定位和解决问题,提高应用的稳定性和用户体验。