当前位置: 首页 > article >正文

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

  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;
    }

    @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 对象,该对象包含 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 {
    @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 自定义错误处理

除了使用拦截器进行全局错误处理,还可以通过自定义 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 {
    @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

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

4.2 错误处理模块总结

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

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

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

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

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


http://www.kler.cn/a/584325.html

相关文章:

  • Spring Boot基础使用详解
  • 第十七:go 反射
  • docker安装的es报错了?failed to obtain node locks怎么破~
  • linux - ubuntu 使用时一些小问题整理 --- 持续更新
  • 级联树SELECTTREE格式调整
  • AI智能代码疫苗技术,赋能数字化应用内生安全自免疫
  • 通义万相 2.1 × 蓝耘智算:AIGC 界的「黄金搭档」如何重塑创作未来?
  • 解析富集分析中的过表达分析(ORA):原理、应用与优化
  • Axure PR 9 中继器 05 快捷查询
  • 从3b1b到课堂:教育3D化的理想与现实鸿沟
  • 深度学习 bert与Transformer的区别联系
  • 接入手机后,DeepSeek难“转正”
  • Pytorch中矩阵乘法使用及案例
  • Manus(一种AI代理或自动化工具)与DeepSeek(一种强大的语言模型或AI能力)结合使用任务自动化和智能决策
  • 视频理解之Actionclip(论文宏观解读)
  • 小米路由器SSH下安装DDNS-GO
  • 【学习笔记】语言模型的发展历程
  • vue2安装scss
  • Java 断言(Assert)机制
  • 西门子S7-1200 PLC远程调试技术方案(巨控GRM532模块)