Android Retrofit 框架日志与错误处理模块深度剖析(七)

news/2025/3/14 13:23:48/

一、引言

在 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 方法中。该方法会根据当前的日志级别记录请求和响应的详细信息,具体步骤如下:

  1. 请求信息记录

    • 记录请求的基本信息,如请求方法、URL 和协议。
    • 如果日志级别为 HEADERSBODY,记录请求的头部信息。
    • 如果日志级别为 BODY,且请求体是纯文本,记录请求体的内容。
  2. 执行请求:调用 chain.proceed(request) 方法执行请求,并获取响应。

  3. 响应信息记录

    • 记录响应的基本信息,如响应码、响应消息、URL 和请求耗时。
    • 如果日志级别为 HEADERSBODY,记录响应的头部信息。
    • 如果日志级别为 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 对象,该对象包含 onResponseonFailure 两个方法。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

OkHttpCallCall 接口的具体实现类,负责实际的网络请求。以下是 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 方法的主要步骤如下:

  1. 检查请求是否已经执行,如果已经执行则抛出异常。
  2. 创建 OkHttp 的 Call 对象,如果创建过程中出现异常,则调用 callback.onFailure 方法。
  3. 异步执行 OkHttp 的 Call 对象,在 onFailure 方法中处理请求过程中的异常,在 onResponse 方法中解析响应数据。
  4. 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 自定义错误处理

除了使用拦截器进行全局错误处理,还可以通过自定义 CallAdapterConverter 来实现更灵活的错误处理。

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 方法返回一个自定义的 CallAdapterErrorHandlingCall 是一个自定义的 Call 实现类,在 executeenqueue 方法中处理请求的异常和错误响应。

要使用这个自定义的 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 实现日志记录,通过设置不同的日志级别(NONEBASICHEADERSBODY)可以控制日志的详细程度。
  • 自定义日志记录器:开发者可以自定义日志记录器,将日志记录到文件或其他存储介质中,满足不同的日志记录需求。

4.2 错误处理模块总结

  • 异步和同步请求错误处理:在异步请求中,通过 Callback 接口的 onResponseonFailure 方法处理请求的成功和失败;在同步请求中,通过 try-catch 块捕获并处理 IOException 异常。

  • 全局错误处理:可以使用拦截器实现全局错误处理,统一处理不同类型的错误响应和网络异常,提高代码的可维护性。

  • 自定义错误处理:通过自定义 CallAdapterConverter 可以实现更灵活的错误处理,例如在请求执行和响应转换过程中处理异常。

  • 重试机制:实现重试拦截器可以在请求失败时进行重试,提高请求的成功率,特别是在网络不稳定的情况下。

通过深入理解和掌握 Retrofit 的日志与错误处理模块,开发者可以更好地监控网络请求的状态,快速定位和解决问题,提高应用的稳定性和用户体验。


http://www.ppmy.cn/news/1579050.html

相关文章

Shader中着色器的编译目标级别

1. # pragma target x.0 2. # pragma require xxx 支持的“#pragma target”名称 以下是支持的着色器模型列表&#xff0c;其中包含大致增加的功能集&#xff08;在某些情况下对于平台/GPU 的要求更高&#xff09;&#xff1a; #pragma target 2.0 适用于 Unity 支持的所有平…

IvorySQL 4.4 发布

IvorySQL 4.4 已于 2025 年 3 月 10 日正式发布。新版本全面支持 PostgreSQL 17.4&#xff0c;新增多项新功能&#xff0c;并修复了已知问题。 增强功能 PostgreSQL 17.3 增强功能 加强 PQescapeString 及相关函数对无效编码输入字符串的防护。恢复在连接请求中出现的数据库…

蓝桥杯备考:图论初解

1&#xff1a;图的定义 我们学了线性表和树的结构&#xff0c;那什么是图呢&#xff1f; 线性表是一个串一个是一对一的结构 树是一对多的&#xff0c;每个结点可以有多个孩子&#xff0c;但只能有一个父亲 而我们今天学的图&#xff01;就是多对多的结构了 V表示的是图的顶点集…

机器学习中常用的避免过拟合的方法有哪些

在机器学习和深度学习中&#xff0c;避免过拟合是提高模型泛化能力的关键。以下是一些常用的避免过拟合的方法&#xff1a; 1. ​增加数据量 ​原理&#xff1a;更多的数据可以帮助模型学习到数据的本质规律&#xff0c;而不是噪声。​方法&#xff1a; 收集更多的真实数据。使…

ClickHouse 通过 ​*ARRAY JOIN* 结合 ​Map 类型的内置函数取数值

在 ClickHouse 中&#xff0c;可以通过 ​ARRAY JOIN 结合 ​Map 类型的内置函数&#xff0c;将 Map 字段的键值对展开为多行数据。以下是具体操作方法和示例&#xff1a; 一、使用 mapKeys 和 mapValues 展开 Map 1. 核心语法 SELECT id, key, value FROM your_table ARRAY …

【21】单片机编程核心技巧:if语句逻辑与真假判断

【21】单片机编程核心技巧&#xff1a;if语句逻辑与真假判断 七律 条件分野 if语句判真假&#xff0c;括号条件定乾坤。 非零为真零为假&#xff0c;大括号内藏玄门。 省略虽简风险在&#xff0c;代码规范护本根。 单片逻辑由心控&#xff0c;条件分支自成文。 注释&#xf…

【Node.js入门笔记5---fs文件信息与元数据】

Node.js入门笔记5 Node.js---fs 文件信息与元数据一、文件信息与元数据1.fs.stat() / fs.statSync()&#xff1a;获取文件或目录的详细信息&#xff08;大小、类型、修改时间等&#xff09;。异步同步 2.fs.access()&#xff1a;检查文件是否存在或是否有访问权限。3.fs.symlin…

2024 年第四届高校大数据挑战赛-赛题 A:岩石的自动鉴定

问题1&#xff1a;沉积岩薄片识别模型设计问题分析核心任务&#xff1a;基于“南京大学沉积岩教学薄片照片数据集”&#xff0c;构建多类别分类模型&#xff0c;区分火山碎屑岩、砂岩、泥页岩等9类沉积岩。特征提取需求&#xff1a; 颜色特征&#xff1a;矿物成分差异导致偏光下…