2. 自定义拦截器基础实现

在 OkHttp 的拦截器链中,每个拦截器都是一个 Interceptor 接口的实现。它负责在请求发送前或响应返回后执行自定义逻辑,例如添加公共头、日志记录、参数加密、隐私字段过滤等。本章将从接口契约、调用链路、请求/响应修改三方面讲解基础实现方式。

2.1 Interceptor 接口与 chain.proceed() 原理

OkHttp 定义了一个核心接口 okhttp3.Interceptor,其中只有一个方法:

public interface Interceptor {
    @NonNull Response intercept(@NonNull Chain chain) throws IOException;
}

参数 Chain 代表整个拦截器链。调用 chain.proceed(request) 会将当前 Request 传递给链中的下一个拦截器,并最终执行网络 I/O 或缓存命中,返回 Response。在 intercept 方法中,开发者可以在调用 proceed 之前修改请求,在调用之后修改响应。

关键语义:每个拦截器应当恰好调用一次 chain.proceed(),否则会断开拦截器链,导致请求无法完成或响应无法返回。

2.2 自定义拦截器的实现流程

实现一个自定义拦截器通常包含以下步骤:

  1. 实现 Interceptor 接口:创建类实现 intercept(Chain) 方法。
  2. 获取原始请求:通过 chain.request() 拿到不可变的 Request 对象。
  3. 修改请求(可选):使用 Request.Builder 在原有请求基础上添加/删除 Header、修改 URL、替换 Body 等。
  4. 调用 chain.proceed(newRequest):传入修改后的请求,获得响应。
  5. 修改响应(可选):使用 Response.Builder 修改响应头、状态码、Body 等。
  6. 返回最终 Response。
一个完整的隐私过滤拦截器示例(过滤掉请求中的用户手机号 Header):
public class PrivacyFilterInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        // 1. 获取原始请求
        Request originalRequest = chain.request();

        // 2. 检查并移除隐私 Header
        Request.Builder builder = originalRequest.newBuilder();
        if (originalRequest.header("X-User-Phone") != null) {
            builder.removeHeader("X-User-Phone")
                   .addHeader("X-Phone-Filtered", "true");
        }

        // 3. 构建新请求
        Request filteredRequest = builder.build();

        // 4. 传递给下一拦截器
        Response response = chain.proceed(filteredRequest);

        // 5. 修改响应:添加一条标记
        return response.newBuilder()
                .header("X-Privacy-Filtered", "true")
                .build();
    }
}

2.3 Request/Response 的可修改范围

通过 Request.Builder 和 Response.Builder 只能创建新的不可变对象,无法直接修改原始对象。以下是可修改的字段清单:

对象可修改项示例方法
Request.BuilderURL、Method、Headers、Body、Tag、CacheControl.url()、.header(name, value)、.removeHeader(name)、.post(body)
Response.BuilderCode、Message、Headers、Body、Protocol、Handshake、CacheResponse.code()、.message()、.header(name, value)、.body(responseBody)
注意:如果尝试修改 Request 的 URL 或 Method,可能会导致路由或缓存策略改变,务必在理解 OkHttp 内部机制后操作。对于 Body 的替换,需确保新旧 Body 的 Content-Type 一致或适配。

2.4 内联流程图

下图展示了自定义拦截器在 OkHttp 链中的位置以及请求/响应修改的典型路径:

自定义拦截器工作流程 用户层拦截器 PrivacyFilterInterceptor 网络层拦截器 chain.proceed() chain.proceed() ① 修改请求 移除隐私Header / 加密参数 ③ 修改响应 添加过滤标记 / 脱敏 Body 核心时机:拦截器链中 proceed 之前/之后 proceed 前 → 修改请求;proceed 后 → 修改响应

2.5 调试与验证

在集成自定义拦截器后,建议通过以下方式验证隐私过滤是否生效:

  • 日志拦截器:在自定义拦截器之后添加一个日志拦截器,打印最终发出的请求 Header 和响应 Header。
  • 单元测试:使用 MockWebServer 模拟服务器,断言请求中不包含敏感字段。
  • 抓包工具:通过 Charles / Wireshark 确认实际网络包中已移除隐私数据。

2.6 常见错误与最佳实践

错误场景原因解决方案
未调用 chain.proceed()拦截器直接返回 new Response.Builder()...build()必须调用一次 chain.proceed(),否则后续拦截器不可达
多次调用 chain.proceed()导致重复网络请求只调用一次,将结果暂存后返回
修改后未重新构建 Header直接使用 request.header() 无法移除使用 newBuilder().removeHeader().build()
修改了 URL 但未考虑缓存Url 变更可能导致缓存 key 不匹配若需修改 URL,考虑在应用层拦截器而非网络层

2.7 性能与稳定性优化

  • 避免在拦截器中执行耗时 I/O(如磁盘写入、网络调用),否则会阻塞线程池。
  • 拦截器实例只创建一次,在 OkHttpClient.Builder 中复用,减少 GC。
  • 对 Body 的修改(如替换为脱敏文本)务必保持 Content-Length 一致或使用 BufferedSink 流式写入。
  • 异常处理:在 intercept 方法中捕获 IOException 并决定是否重试或返回 fallback 响应。
建议:始终将隐私过滤拦截器放在应用层拦截器中(通过 addInterceptor() 添加),以确保在所有重定向和重试中生效。网络层拦截器(通过 addNetworkInterceptor())在失败重试时会再次触发,可能导致重复过滤。