4. 自定义 Interceptor 实现:实现 Interceptor 接口、intercept 方法、Chain.proceed 调用

4.1 原理概述

OkHttp 的拦截器链(Interceptor Chain)采用责任链模式。每个实现了 Interceptor 接口的对象都可以在请求发出前 拦截 并 修改 请求,在响应返回后 处理 响应数据。自定义拦截器是承载埋点状态嵌入的最小工作单元——你可以在 intercept() 方法中注入上下文(如用户ID、会话凭证、请求耗时标记等),再通过 chain.proceed() 将请求传递给链中下一个拦截器。

核心接口 / 方法 作用
Interceptor 接口 定义拦截器契约,仅包含一个方法 intercept(Chain)
Chain (拦截器链) 持有请求对象、连接信息,提供 proceed(Request) 执行下一个拦截器
request() (Chain 方法) 获取当前待处理的 Request 对象
proceed(Request) 将请求继续沿链向下传递,返回 Response

4.2 实现流程

  1. 创建类 —— 实现 okhttp3.Interceptor 接口。
  2. 重写 intercept(Chain) —— 在其中获取请求、注入埋点状态、调用 chain.proceed() 并捕获响应。
  3. 返回 Response —— 处理完响应(如添加响应头、记录日志)后必须返回。
  4. 注册到 OkHttpClient —— 根据需求添加到应用拦截器列表或网络拦截器列表。
关键提醒: 被拦截的 Request 对象是不可变的(immutable),必须通过其 newBuilder() 创建新的 Request 对象,再传给 proceed()。

4.3 代码实现示例

public class StateEmbeddingInterceptor implements Interceptor {
    private final String userId;
    private final String sessionToken;

    public StateEmbeddingInterceptor(String userId, String sessionToken) {
        this.userId = userId;
        this.sessionToken = sessionToken;
    }

    @NonNull
    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        // 1. 获取原始请求
        Request originalRequest = chain.request();

        // 2. 构造携带埋点状态的新请求
        Request modifiedRequest = originalRequest.newBuilder()
                .header("X-User-Id", userId)
                .header("X-Session-", sessionToken)
                .header("X-Embedded-Ts", String.valueOf(System.currentTimeMillis()))
                .build();

        // 3. 传递给下一个拦截器
        Response response = chain.proceed(modifiedRequest);

        // 4. 对响应进行二次处理(可选)
        // 例如:读取响应埋点标识、记录耗时
        Response.Builder responseBuilder = response.newBuilder()
                .header("X-Monitored", "true");

        return responseBuilder.build();
    }
}

4.4 注册到 OkHttpClient

将自定义拦截器添加到客户端时,需区分 应用拦截器(addInterceptor)与 网络拦截器(addNetworkInterceptor):

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new StateEmbeddingInterceptor("uid_1001", "_abc123"))
        // 或 .addNetworkInterceptor(...)
        .build();
建议: 埋点状态嵌入通常放在应用拦截器层(addInterceptor),因为它在重试和重定向之前就执行,且对每一个应用发出的请求都生效。

4.5 拦截器链调用示意(SVG)

自定义拦截器在 OkHttp 链中的位置与调用流程 自定义拦截器在链中的调用流程 应用拦截器A 自定义拦截器 (埋点状态嵌入) 应用拦截器C 网络拦截器D Web Server 关键动作 1. 从 chain.request() 获取原请求 2. 构造带埋点头的 newRequest 3. 调用 chain.proceed(newRequest) 4. 对 Response 二次加工并返回

4.6 常见错误与避坑

  • 忘记调用 chain.proceed() —— 如果不调用,请求将永远得不到响应,导致 IOException 或界面卡死。
  • 修改了原始 Request 对象 —— 直接对 chain.request() 做 setter 会抛 UnsupportedOperationException,必须使用 newBuilder() 创建副本。
  • 在拦截器中抛出未捕获的异常 —— 拦截器运行在 OkHttp 线程池中,应妥善处理 IOException 并向外抛出,以免破坏链状态。
  • 与网络拦截器混淆 —— 网络拦截器会收到重定向和重试后的请求,可能导致埋点状态重复添加或丢失,需根据需求选择注册方法。

4.7 性能与稳定性建议

性能关注点: 不要在 intercept() 中执行耗时操作(如文件读写、复杂加解密),否则会阻塞 OkHttp 的异步分发。若必须执行,请考虑使用自定义线程池或 异步拦截器模式(不过 OkHttp 官方不推荐过度复杂的异步拦截器)。
  • 埋点头字段保持轻量(如用户ID、时间戳),避免携带大对象(base64 大图、序列化 JSON)。
  • 对 System.currentTimeMillis() 采集的埋点时间戳,注意时区统一(推荐 UTC);也可改为 System.nanoTime() 计算相对耗时。
  • 建议为自定义拦截器添加日志开关,在调试阶段输出拦截前后的请求/响应头,方便定位问题。