
在生产实践中,浅出常常会遇到这样的源码用实场景 :需要针对某一类 Http 请求做统一的处理 ,例如在 Header 里添加请求参数或者修改请求响应等等 。解析及应践这类问题的深入一种比较优雅的解决方案是使用拦截器来对请求和响应做统一处理。
在 Android 和 Java 世界里 OkHttp 凭借其高效性和易用性被广泛使用。浅出作为一款优秀的源码用实开源 Http 请求框架,深入了解它的解析及应践实现原理 ,可以学习优秀软件的深入设计和编码经验 ,免费模板帮助我们更好到地使用它的浅出特性,并且有助于特殊场景下的源码用实问题排查。本文尝试从源代码出发探究 OkHttp 的解析及应践基本原理 ,并列举了一个简单的深入例子说明拦截器在我们项目中的实际应用。本文源代码基于 OkHttp 3.10.0 。浅出
OkHttp 可以用来发送同步或异步的请求,异步请求与同步请求的主要区别在于异步请求会交由线程池来调度请求的执行。高防服务器使用 OkHttp 发送一个同步请求的代码相当简洁 ,示例代码如下:
同步 GET 请求示例
复制// 1.创建OkHttpClient客户端 OkHttpClient client = new OkHttpClient(); public String getSync(String url) throws IOException { OkHttpClient client = new OkHttpClient(); // 2.创建一个Request对象 Request request = new Request.Builder() .url(url) .build(); // 3.创建一个Call对象并调用execute()方法 try (Response response = client.newCall(request).execute()) { return response.body().string(); } }1.2.3.4.5.6.7.8.9.10.11.12.13.其中 execute() 方法是请求发起的入口,RealCall 对象的 execute() 方法的源代码如下 :
RealCall 的 execute() 方法源代码
复制@Override public Response execute() throws IOException { synchronized (this) { // 同步锁定当前对象,将当前对象标记为“已执行” if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); // 捕获调用栈 eventListener.callStart(this); // 事件监听器记录“调用开始”事件 try { client.dispatcher().executed(this); // 调度器将当前对象放入“运行中”队列 Response result = getResponseWithInterceptorChain(); // 通过拦截器发起调用并获取响应 if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { eventListener.callFailed(this, e); // 异常时记录“调用失败事件” throw e; } finally { client.dispatcher().finished(this); // 将当前对象从“运行中”队列移除 } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.execute() 方法首先将当前请求标记为“已执行” ,然后会为重试跟踪拦截器添加堆栈追踪信息,接着事件监听器记录“调用开始”事件 ,调度器将当前对象放入“运行中”队列 ,之后通过拦截器发起调用并获取响应,最后在 finally 块中将当前请求从“运行中”队列移除 ,异常发生时事件监听器记录“调用失败”事件。香港云服务器其中关键的方法 是
getResponseWithInterceptorChain() 其源代码如下:
复制Response getResponseWithInterceptorChain() throws IOException { // 构建一个全栈的拦截器列表 List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, ……); return chain.proceed(originalRequest); }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.该方法中按照特定的顺序创建了一个有序的拦截器列表 ,之后使用拦截器列表创建拦截器链并发起 proceed() 方法调用 。在chain.proceed() 方法中会使用递归的方式将列表中的拦截器串联起来依次对请求对象进行处理 。拦截器链的实现是 OkHttp 的一个巧妙所在,在后文我们会用一小节专门讨论 。在继续往下分析之前 ,源码下载通过以上的代码片段我们已经大致看到了一个请求发起的整体流程 。
2.2 OkHttp 核心执行流程一个 OkHttp 请求的核心执行过程如以下流程图所示:

图 2-1 OkHttp请求执行流程图
图中各部分的含义和作用如下:
OkHttpClient :是整个 OkHttp 的核心管理类 ,从面向对象的抽象表示上来看它代表了客户端本身,是请求的调用工厂,用来发送请求和读取响应。在大多数情况下这个类应该是被共享的亿华云,因为每个 Client 对象持有自己的连接池和线程池 。重复创建则会造成在空闲池上的资源浪费 。Client对象可以通过默认的无参构造方法创建也可以通过 Builder 创建自定义的 Client 对象。Client 持有的线程池和连接池资源在空闲时可以自动释放无需客户端代码手动释放,在特殊情况下也支持手动释放。Request:一个 Request 对象代表了一个 Http 请求。它包含了请求地址 url,请求方法类型 method ,请求头 headers ,请求体 body 等属性 ,源码库该对象具有的属性普遍使用了 final 关键字来修饰,正如该类的说明文档中所述 ,当这个类的 body 为空或者 body 本身是不可变对象时,这个类是一个不可变对象。Response:一个 Response 对象代表了一个 Http 响应。这个实例对象是一个不可变对象 ,只有 responseBody 是一个可以一次性使用的值 ,其他属性都是不可变的 。RealCall :一个 RealCall 对象代表了一个准备好执行的请求调用。它只能被执行一次 。同时负责了调度和责任链组织的两大重任。Dispatcher:调度器 。它决定了异步调用何时被执行 ,内部使用 ExecutorService 调度执行 ,支持自定义 Executor。EventListener:事件监听器。抽象类 EventListener 定义了在一个请求生命周期中记录各种事件的方法,通过监听各种事件,可以用来捕获应用程序 HTTP 请求的执行指标 。从而监控 HTTP 调用的频率和性能。Interceptor :拦截器。对应了软件设计模式中的拦截器模式,拦截器可用于改变、增强软件的常规处理流程,该模式的核心特征是对软件系统的改变是透明的和自动的 。OkHttp 将整个请求的复杂逻辑拆分成多个独立的拦截器实现 ,通过责任链的设计模式将它们串联到一起,完成发送请求获取响应结果的过程。2.3 OkHttp 整体架构通过进一步阅读 OkHttp 源码 ,可以看到 OkHttp 是一个分层的结构。软件分层是复杂系统设计的常用手段,通过分层可以将复杂问题划分成规模更小的子问题 ,分而治之 。同时分层的设计也有利于功能的封装和复用 。OkHttp 的架构可以分为:应用接口层,协议层 ,连接层,缓存层,I/O层 。不同的拦截器为各个层次的处理提供调用入口 ,拦截器通过责任链模式串联成拦截器链,从而完成一个Http请求的完整处理流程 。如下图所示:

图 2-2 OkHttp架构图(图片来自网络)
2.4 OkHttp 拦截器的种类和作用OkHttp 的核心功能是通过拦截器来实现的,各种拦截器的作用分别为 :
client.interceptors