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

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

一、引言

在 Android 开发中,网络请求是一项常见且重要的任务。Retrofit 作为一个强大的类型安全的 HTTP 客户端,被广泛应用于各种 Android 项目中。日志与错误处理模块在 Retrofit 框架中扮演着至关重要的角色,它们有助于开发者在开发和调试过程中监控网络请求的详细信息,以及在出现问题时快速定位和解决错误。本文将深入 Retrofit 框架的源码,详细分析其日志与错误处理模块的实现原理。

二、日志模块分析

2.1 日志模块概述

Retrofit 本身并不直接提供日志功能,而是借助 OkHttp 来实现日志记录。OkHttp 是一个高效的 HTTP 客户端,Retrofit 默认使用 OkHttp 作为底层的网络请求库。OkHttp 提供了一个 HttpLoggingInterceptor 拦截器,通过该拦截器可以方便地实现请求和响应的日志记录。

2.2 使用 HttpLoggingInterceptor 记录日志

以下是一个简单的示例,展示如何在 Retrofit 中使用 HttpLoggingInterceptor 记录日志:

java

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

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

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

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

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

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 对象,该对象包含 onResponseonFailure 两个方法。onResponse 方法在请求完成后被调用,如果请求成功(响应码为 200 - 300),则可以通过 response.body() 获取响应数据;如果请求失败,则可以通过 response.errorBody() 获取错误响应体。onFailure 方法在请求过程中出现异常时被调用,如网络连接异常、超时异常等。

3.3 同步请求的错误处理

同步请求使用 execute 方法,需要在 try-catch 块中处理异常:

java

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

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

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

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

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

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

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 自定义错误处理

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

3.6.1 自定义 CallAdapter

java

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

要使用这个自定义的 CallAdapter,可以在创建 Retrofit 实例时添加:

java

java 复制代码
Retrofit retrofit = new Retrofit.Builder()
      .baseUrl(baseUrl)
      .addCallAdapterFactory(new ErrorHandlingCallAdapterFactory())
      .addConverterFactory(GsonConverterFactory.create())
      .build();
3.6.2 自定义 Converter

java

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

java 复制代码
Retrofit retrofit = new Retrofit.Builder()
      .baseUrl(baseUrl)
      .addConverterFactory(new ErrorHandlingConverterFactory())
      .addConverterFactory(GsonConverterFactory.create())
      .build();

3.7 重试机制

在网络请求中,有时候请求失败可能是由于临时的网络问题导致的,这时可以实现一个重试机制来提高请求的成功率。以下是一个简单的重试拦截器示例:

java

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

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

4.2 错误处理模块总结

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

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

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

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

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

相关推荐
Devil枫27 分钟前
Kotlin高级特性深度解析
android·开发语言·kotlin
ChinaDragonDreamer29 分钟前
Kotlin:2.1.20 的新特性
android·开发语言·kotlin
雨白11 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹13 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空14 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭15 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日16 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安16 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑16 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟20 小时前
CTF Web的数组巧用
android