一、引言
在 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 实例,并配置 OkHttpClientretrofit = 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;}@Overridepublic 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>() {@Overridepublic 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();}}}@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 实例,并配置 OkHttpClientretrofit = 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 {@Overridepublic 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>>() {@Overridepublic Type responseType() {return delegate.responseType();}@Overridepublic 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;}@Overridepublic retrofit2.Response<T> execute() throws java.io.IOException {try {return delegate.execute();} catch (java.io.IOException e) {// 处理同步请求的异常handleNetworkException(e);throw e;}}@Overridepublic void enqueue(final retrofit2.Callback<T> callback) {delegate.enqueue(new retrofit2.Callback<T>() {@Overridepublic void onResponse(Call<T> call, retrofit2.Response<T> response) {if (!response.isSuccessful()) {// 处理异步请求的错误响应handleErrorResponse(response);}callback.onResponse(call, response);}@Overridepublic void onFailure(Call<T> call, Throwable t) {if (t instanceof java.io.IOException) {// 处理异步请求的网络异常handleNetworkException((java.io.IOException) t);}callback.onFailure(call, t);}});}@Overridepublic boolean isExecuted() {return delegate.isExecuted();}@Overridepublic void cancel() {delegate.cancel();}@Overridepublic boolean isCanceled() {return delegate.isCanceled();}@Overridepublic Call<T> clone() {return new ErrorHandlingCall<>(delegate.clone());}@Overridepublic 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 {@Overridepublic 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>() {@Overridepublic 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;}@Overridepublic 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 实例,并配置 OkHttpClientretrofit = 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 的日志与错误处理模块,开发者可以更好地监控网络请求的状态,快速定位和解决问题,提高应用的稳定性和用户体验。