作者:vivo 互联网服务器团队 - Tie Qinrui
OkHttp 在 Java 和 Android 世界中被宽泛应用,深刻学习源代码有助于把握软件个性和进步编程程度。
本文首先从源代码动手简要剖析了一个申请发动过程中的外围代码,接着通过流程图和架构图概括地介绍了 OkHttp 的整体构造,重点剖析了拦截器的责任链模式设计,最初列举了 OkHttp 拦截器在我的项目中的理论利用。
一、背景介绍
在生产实践中,经常会遇到这样的场景:须要针对某一类 Http 申请做对立的解决,例如在 Header 里增加申请参数或者批改申请响应等等。这类问题的一种比拟优雅的解决方案是应用拦截器来对申请和响应做对立解决。
在 Android 和 Java 世界里 OkHttp 凭借其高效性和易用性被宽泛应用。作为一款优良的开源 Http 申请框架,深刻理解它的实现原理,能够学习优良软件的设计和编码教训,帮忙咱们更好到地应用它的个性,并且有助于非凡场景下的问题排查。本文尝试从源代码登程探索 OkHttp 的基本原理,并列举了一个简略的例子阐明拦截器在咱们我的项目中的理论利用。本文源代码基于 OkHttp 3.10.0。
二、OkHttp 基本原理
2.1 从一个申请示例登程
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();}
}
其中 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); // 将以后对象从“运行中”队列移除
}
}
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);
}
该办法中依照特定的程序创立了一个有序的拦截器列表,之后应用拦截器列表创立拦截器链并发动 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:由开发者设置的拦截器,会在所有的拦截器解决之前进行最早的拦挡解决,可用于增加一些公共参数,如自定义 header、自定义 log 等等。
- RetryAndFollowUpInterceptor:次要负责进行重试和重定向的解决。
- BridgeInterceptor:次要负责申请和响应的转换。把用户结构的 request 对象转换成发送到服务器 request 对象,并把服务器返回的响应转换为对用户敌对的响应。
- CacheInterceptor:次要负责缓存的相干解决,将 Http 的申请后果放到到缓存中,以便在下次进行雷同的申请时,间接从缓存中读取后果,进步响应速度。
- ConnectInterceptor:次要负责建设连贯,建设 TCP 连贯或者 TLS 连贯。
- client.networkInterceptors:由开发者设置的拦截器,实质上和第一个拦截器相似,然而因为地位不同,所以用途也不同。
- CallServerInterceptor:次要负责网络数据的申请和响应,也就是理论的网络 I / O 操作。将申请头与申请体发送给服务器,以及解析服务器返回的 response。
除了框架提供的拦截器外,OkHttp 反对用户自定义拦截器来对申请做加强解决,自定义拦截器能够分为两类,别离是应用程序拦截器和网络拦截器,他们发挥作用的层次结构如下图:
图 2-3 拦截器(图片来自 OkHttp 官网)
不同的拦截器有不同的实用场景,他们各自的优缺点如下:
应用程序拦截器
- 无需放心重定向和重试等两头响应。
- 总是被调用一次,即便 HTTP 响应是从缓存中提供的。
- 能够察看到应用程序的原始申请。不关怀 OkHttp 注入的标头。
- 容许短路而不调用 Chain.proceed()办法。
- 容许重试并屡次调用 Chain.proceed()办法。
- 能够应用 withConnectTimeout、withReadTimeout、withWriteTimeout 调整呼叫超时。
网络拦截器
- 可能对重定向和重试等两头响应进行操作。
- 缓存响应不会调用。
- 能够察看到通过网络传输的原始数据。
- 能够拜访携带申请的链接。
2.5 责任链模式串联拦截器调用
OkHttp 内置了 5 个外围的拦截器用来实现申请生命周期中的要害解决,同时它也反对增加自定义的拦截器来加强和扩大 Http 客户端,这些拦截器通过责任链模式串联起来,使得的申请能够在不同拦截器之间流转和解决。
2.5.1 责任链模式
责任链模式 是一种行为设计模式,容许将申请沿着解决者链发送。收到申请后,每个解决者均可对申请进行解决,或将其传递给链上的下一个解决者。
图 2-4 责任链(图片来自网络)
实用场景 包含:
- 当程序须要应用不同形式解决不同品种的申请时
- 当程序必须按程序执行多个解决者时
- 当所须要的解决者及其程序必须在运行时进行扭转时
长处:
- 能够管制申请解决的程序
- 可对发动操作和执行操作的类进行解耦。
- 能够在不更改现有代码的状况下在程序中新增解决者。
2.5.2 拦截器的串联
责任链的入口从第一个 RealInterceptorChain 对象的 proceed() 办法调用开始。这个办法的设计十分奇妙,在残缺的 proceed() 办法里会做一些更为谨严的校验,去掉这些校验后该办法的外围代码如下:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {if (index >= interceptors.size()) throw new AssertionError();
// ……
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// ……
return response;
}
这段代码能够看成三个步骤:
- 索引判断。index 初始值为 0,它批示了拦截器对象在列表中的索引程序,每执行一次 proceed 办法该参数自增 1,当索引值大于拦截器列表的索引下标时异样退出。
- 创立 下一个责任链对象。
- 依照索引程序 获取 一个拦截器,并调用 intercept() 办法。
- 责任链串联
独自看这个办法仿佛并不能将所有拦截器都串联起来,串联的关键在于 intercept() 办法,intercept() 办法是实现 interceptor 接口时必须要实现的办法,该办法持有下一个责任链 对象 chain,在拦截器的实现类里只须要在 intercept() 办法里的适当中央再次调用 chain.proceed() 办法,程序指令便会从新回到以上代码片段,于是就能够触发对于下一个拦截器的查找和调用了,在这个过程中拦截器对象在列表中的先后顺序十分重要,因为拦截器的调用程序就是其在列表中的索引程序。
- 递归办法
从另一个角度来看,proceed() 办法能够看成是一个递归办法。递归办法的根本定义为“函数的定义中调用函数本身”,尽管 proceed() 办法没有间接调用本身,然而除了最初一个拦截器以外,拦截器链中的其余拦截器都会在适当的地位调用 chain.proceed() 办法,责任链对象和拦截器对象合在一起则组成了一个本人调用本人的逻辑循环。依照笔者集体了解,这也是为什么源代码里 Chain 接口被设计成 Interceptor 接口的外部接口,在了解这段代码的时候要把它们两个接口当成一个整体来看,从这样的角度看的话,这样的接口设计是合乎“高内聚”的准则的。
拦截器 interceptor 和责任链 chain 的关系如下图:
图 2-5 Interceptor 和 Chain 的关系图
三、OkHttp 拦截器在我的项目中的利用
在咱们的我的项目中,有一类申请须要在申请头 Header 中增加认证信息,应用拦截器来实现能够极大地简化代码,进步代码可读性和可维护性。外围代码只须要实现合乎业务须要的拦截器如下:
- 增加申请头的拦截器
public class EncryptInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {Request originRequest = chain.request();
// 计算认证信息
String authorization = this.encrypt(originRequest);
// 增加申请头
Request request = originRequest.newBuilder()
.addHeader("Authorization", authorization)
.build();
// 向责任链前面传递
return chain.proceed(request);
}
}
之后在创立 OkHttpClient 客户端的时候,应用 addInterceptor() 办法将咱们的拦截器注册成应用程序拦截器,即可实现主动地、无感地向申请头中增加实时的认证信息的性能。
- 注册应用程序拦截器
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new EncryptInterceptor())
.build();
四、回顾总结
OkHttp 在 Java 和 Android 世界中被宽泛应用,通过应用 OkHttp 拦截器能够解决一类问题——针对一类申请对立批改申请或响应内容。深刻理解 OkHttp 的设计和实现不仅能够帮忙咱们学习优良开源软件的设计和编码教训,也有利于更好地应用软件个性以及对非凡场景下问题的排查。本文尝试从一个同步 GET 申请的例子开始,首先通过源代码片段简要剖析了一个申请发动过程中波及的外围代码,接着用流程图的模式总结了申请执行过程,而后用架构图展现了 OkHttp 的分层设计,介绍了各种拦截器的用处、工作档次及优缺点,之后着重剖析了拦截器的责任链模式设计——实质是一个递归调用,最初用一个简略的例子介绍了 OkHttp 拦截器在理论生产场景中的利用。
参考:
- OkHttp 官网文档
- OkHttp 源码解析系列文章