前言
对于OkHttp我们需要了解一下相关网络的知识哦,所以还不了解的同学们建议先去补充一下网络相关的HTTP、TCP、UDP等知识。(要不我们来更新一篇网络的知识?)
作为我们的主流框架OkHttp的解析,我们准备分以下几个部分来讲述:(基于4.11版本)
- 构建与初始化
- 请求的发起过程
- 核心部分:责任链拦截器
- 整两个小问题聊一聊
构建与初始化
首先我们经常用的两种请求方式:
同步请求:
//1、构建OKHttpClient对象
OkHttpClient client = new OkHttpClient();
//2、构建一个Request对象
Request request = new Request.Builder()
.url(url)
.build();
//3、同步请求
Response response = client.newCall(request).execute();
异步请求:
//1、构建OKHttpClient对象
OkHttpClient client = new OkHttpClient();
//2、构建一个Request对象
Request request = new Request.Builder()
.url(url)
.build();
//3、异步请求
client.newCall(request).enqueue(new Callback() {
@Overridepublic void onFailure(@NotNull Call call, @NotNull IOException e) {
//请求的失败回调
}
@Overridepublic void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
//成功的Response处理
}
});
其实可以看到,我们不管是同步请求,还是异步的请求。
都需要:
- 创建OKHttpClient客户端。
- 创建一个Request对象。
- 通过newCall创建的Call对象处理请求。
那么,构建与初始化,也就是说的是上面这三步了,那就逐个来看看吧。
构建OKHttpClient对象
//1、创建OKHttpClient客户端
OkHttpClient client = new OkHttpClient();
点进去看看(无用代码省略,4.11版本是kotlin哦)
open class OkHttpClient internal constructor(
builder: Builder
)
constructor() : this(Builder())
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()
internal var connectionPool: ConnectionPool = ConnectionPool()
.......
.......
靠!!靠!!有是Builder,又是构建者模式,是不是大框架都是构建者模式啊?
确实是这样,因为大框架都支持你在初始化的时候去自定义一些功能,所以大多数都采用了构建者模式、还有常见的单例模式。
并且一些重要的对象,也会在使用构建者模式的时候进行初始化,比如下面这些:(列举部分)
class Builder constructor() {
//调度器
internal var dispatcher: Dispatcher = Dispatcher()
//连接池
internal var connectionPool: ConnectionPool = ConnectionPool()
//整体流程拦截器
internal val interceptors: MutableList<Interceptor> = mutableListOf()
//网络流程拦截器
internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
//流程监听器
internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
//连接失败时是否重连
internal var retryOnConnectionFailure = true
//是否重定向
internal var followRedirects = true
//缓存设置
internal var cache: Cache? = null
//代理设置
internal var proxy: Proxy? = null
//请求超时
internal var callTimeout = 0
//连接超时
internal var connectTimeout = 10_000
//读取超时
internal var readTimeout = 10_000
//写入超时
internal var writeTimeout = 10_000
当然,既然构建者中创建了这些元素,当然也就意味着我们可以对他们进行自定义的构建。
例如:来设置我们的连接超时时间和请求超时时间。
OkHttpClient okHttpClient = new OkHttpClient()
.newBuilder()
.connectTimeout(Duration.ofDays(60000))
.callTimeout(Duration.ofDays(60000))
.build();
构建一个Request对象
//2、构建一个Request对象
Request request = new Request.Builder()
.url(url)
.build();
其实我们从,调用就可以看的出来,他就是构建了一个请求嘛
来点进去看看
class Request internal constructor(
@get:JvmName("url") val url: HttpUrl, //url
@get:JvmName("method") val method: String, //请求方法,GET/POST
@get:JvmName("headers") val headers: Headers, //请求头
@get:JvmName("body") val body: RequestBody?, //请求的内容
internal val tags: Map<Class<*>, Any>
) {
open class Builder {
internal var url: HttpUrl? = null
internal var method: String
internal var headers: Headers.Builder
internal var body: RequestBody? = null
....
}
真就没什么,只是利用构建者模式,初始化了一些网络请求的重要信息。
构建Call对象
Call call = okHttpClient.newCall(request);
首先点进去Call对象看一下
interface Call : Cloneable {
//返回请求信息
fun request(): Request
//同步请求
@Throws(IOException::class)
fun execute(): Response
//异步请求
fun enqueue(responseCallback: Callback)
//取消请求
fun cancel()
//是否正在请求
fun isExecuted(): Boolean
//是否是取消状态
fun isCanceled(): Boolean
//超时时间
fun timeout(): Timeout
public override fun clone(): Call
fun interface Factory {
fun newCall(request: Request): Call
}
}
其实就是一个面向接口编程的接口类而已。具体的接口功能也在注释中解释了。
其次就是我们通过OkHttpClient对象进行的newCall了。
点进去看看:
override fun newCall(request: Request): Call =
RealCall(this, request, forWebSocket = false)
//RealCall.kt
class RealCall(
val client: OkHttpClient,
/** The application's original request unadulterated by redirects or auth headers. */
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
可以看到在OkHttpClient中我们利用前面构建的OkHttpClient对象和Request对象创建了一个实现了Call接口的RealCall对象。
而从Call接口的功能中我们也看得出来,RealCall是我们将请求发出的重要的掌控者。
那么就继续:请求的发起。
请求的发起过程
发起请求主要为以下两种
- 同步请求
- 异步请求
同步请求
//3、同步请求
Response response = client.newCall(request).execute();
上面已经对OkHttpClient、Request、Call进行了解析,那么我们直接看execute()做了什么呢?
RealCall.kt
override fun execute(): Response {
// 这里用CAS思想,进行锁的保护,使一个call对象只能执行一次execute方法
check(executed.compareAndSet(false, true)) {
"Already Executed" }
timeout.enter()
// 这里主要是个监听器,表示开始进行网络请求了
callStart()
// 重点关注这块
try {
// 利用调度器将当前请求加入到一个同步队列当中
client.dispatcher.executed(this)
// 通过 getResponseWithInterceptorChain() 获得相应结果
return getResponseWithInterceptorChain()
} finally {
// 完成一些收尾工作,在同步请求中,几乎没什么用
client.dispatcher.finished(this)
}
}
异步请求
//3、异步请求
client.newCall(request).enqueue(new Callback() {
@Overridepublic void onFailure(@NotNull Call call, @NotNull IOException e) {
//请求的失败回调
}
@Overridepublic void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
//成功的Response处理
}
});
同样的我们来看一下enqueue中做了什么,因为是异步的所以需要有回调对象的传入。
RealCall.kt
override fun enqueue(responseCallback: Callback) {
// 这里用CAS思想,进行锁的保护,使一个call对象只能执行一次execute方法
check(executed.compareAndSet(false, true)) {
"Already Executed" }
// 这里面依旧加上了监听
callStart()
// 构建一个 AsyncCall对象,再交给dispatcher进行分发流程
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
可以看到都使用了dispatcher去进行了我们的请求的分发。也就是其实重点在这个dispatcher调度器上。那我们来看看分发器具体做了哪些内容呢?
调度器Dispatcher
作为发送请求的第一把手:Dispatcher做了些什么呢?
Dispatcher对于整个发送请求十分重要(特别是对异步请求,因为异步请求的并发还是高的)。
来将Dispatcher切割来看(只看关键的地方)首先是管理的一些重要成员:
- 同步请求的队列
//同步运行时调用、同步请求的管理队列
private val runningSyncCalls = ArrayDeque<RealCall>()
- 异步请求限制条件
//异步请求的最大数量
var maxRequests = 64
//每个主机同时请求的最大数量
var maxRequestsPerHost = 5
- 异步请求管理对列
//异步请求等待队列
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
//异步请求执行队列
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
- 异步请求线程池
//异步请求线程池
private var executorServiceOrNull: ExecutorService? = null
val executorService: ExecutorService
上面我们对同步请求、异步请求只说到了Dispatcher的调度。
那接着来看看Dispatcher中是如何操作的
executed
// 利用调度器将当前请求加入到一个同步队列当中
client.dispatcher.executed(this)
// 通过 getResponseWithInterceptorChain() 获得相应结果
return getResponseWithInterceptorChain()
点进去看看
Dispatcher.kt
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
可以看到,同步请求executed中也没有做什么,只是将请求通过Dispatch加入到了Dispatch维护的同步请求队列中。
正在的请求发生在后面的return getResponseWithInterceptorChain()。也就是拦截器去处理这个同步请求队列。并返回相应的结果。
enqueue
异步请求相对于同步请求还是比较复杂的。其中Dispatch也发挥着大作用。
// 构建一个 AsyncCall对象,再交给dispatcher进行分发流程
client.dispatcher.enqueue(AsyncCall(responseCallback))
这里先不看AsyncCall。先看一下Dispatcher中对enqueue做了什么
Dispatcher.kt
internal fun enqueue(call: AsyncCall) {
//线程同步
synchronized(this) {
//加到异步的网络请求准备队列中
readyAsyncCalls.add(call)
//复用之前的Call
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
//dispatcher进行分发call任务的主要方法
promoteAndExecute()
}
先看下为什么要复用之前的Call呢?是怎么复用的呢?
- 复用操作的核心是调用findExistingCallWithHost(call.host)方法来查找是否已经存在与当前AsyncCall相同的host的Call。
- 如果存在,就调用call.reuseCallsPerHostFrom(existingCall)方法来共享这两个Call之间的用于控制并发请求的AtomicInteger。
- 避免并发请求导致对同一host的网络连接过多的问题,通过共享控制并发请求的AtomicInteger,将两个请求视为一组,从而限制并发的连接数量。这种做法可以提高性能和减少网络连接的开销。
牛逼!!!!! 真是能省就省啊。
下面说到了Dispatcher的核心方法,也是异步请求超级重要的方法:promoteAndExecute()
Dispatcher.kt
//不仅在开始发起请求时被调用,在任务多的时候,其他任务结束时,也会被调用起来执行。
//纯纯苦力
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
//需要开始执行的任务集合
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
//核心!!! 按行来看
//迭代等待执行异步请求
while (i.hasNext()) {
// 1、下一个
val asyncCall = i.next()
// 2、正在执行异步请求的总任务数不能大于64个
//否则直接退出这个循环,不再将请求加到异步请求队列中
//那么就还安静的在准备队列中了
if (runningAsyncCalls.size >= this.maxRequests) break
// 同一个host的请求数不能大于5
// 否则直接跳过此call对象的添加,去遍历下一个asyncCall对象
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue
i.remove()
// 3、如果拿到了符合条件的asyncCall对象,就将其callPerHost值加1(用于上面的Host判断)
// callPerHost代表了连接同一个host的数量
asyncCall.callsPerHost.incrementAndGet()
// 加到需要开始执行的任务集合中
executableCalls.add(asyncCall)
// 将当前call加到正在执行的异步队列当中
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
// 4、遍历每一个集合中的asyncCall对象
// 将其添加到线程池中,执行它的run方法
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
总结核心:
- 迭代异步准备队列
- 满足条件加入异步运行队列:异步请求总任务数小于64并且同一个host的请求数不能大于5
- 将当前Call请求加入到任务集合中
- 遍历任务集合加入请求线程池,并执行他
(执行的话,那会执行什么呢?)
答:呸!! asyncCall的run方法呗,那还用问?
AsyncCall
好嘞,那让我们来看看我们在异步请求执行时添加的AsyncCall中的Run中有什么呢?
// 构建一个 AsyncCall对象,再交给dispatcher进行分发流程
client.dispatcher.enqueue(AsyncCall(responseCallback))
AsyncCall 是 RealCall 的内部类, 它实现了 Runnable 接口,主要是为了能在线程池中去执行它的 run() 方法。
RealCall.kt
//内部类 AsyncCall
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
.....
.....
// 1、将AsyncCall任务添加到线程池
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
// 2、执行线程池。那就到run函数了
executorService.execute(this)
success = true
} catch (e: RejectedExecutionException) {
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
noMoreExchanges(ioException)
// 2.1、请求失败的回调
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
//收尾工作,finished内部还是调用的promoteAndExecute方法。继续下一个任务
client.dispatcher.finished(this)
}
}
}
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
timeout.enter()
try {
// 3、和同步方法一样,通过链式的拦截器,去处理请求返回结果
val response = getResponseWithInterceptorChain()
signalledCallback = true
// 3.1、请求成功的回调
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
if (signalledCallback) {
Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
} else {
responseCallback.onFailure(this@RealCall, e)
}
} catch (t: Throwable) {
cancel()
if (!signalledCallback) {
val canceledException = IOException("canceled due to $t")
canceledException.addSuppressed(t)
// 3.2、请求失败的回调
responseCallback.onFailure(this@RealCall, canceledException)
}
throw t
} finally {
// 4、收尾工作,finished内部还是调用的promoteAndExecute方法。继续下一个任务
client.dispatcher.finished(this)
}
}
}
}
好了以上就是异步请求的请求流程了,可以看到和同步请求一样,最后还是调用了getResponseWithInterceptorChain去通过链式的拦截器去处理了我们的请求,所以拦截器才是OkHttp的重中之重!!
executorService
在说getResponseWithInterceptorChain之前,上面不止一次提及到了这个异步处理的请求池,大家肯定知道,不就是自定义的线程池吗!!!
是啊,你猜对了,但是看看他自定义的几个参数还是挺关键的。来看看:
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
}
return executorServiceOrNull!!
}
- 参数一:corePoolSize:核心线程数:表示线程池中始终保持的活动线程数量,即使它们处于空闲状态也不会被销毁,默认情况下线程池是空闲的。
- corePoolSize的值为0,表示线程池不保持任何活动线程。
- 参数二:maximumPoolSize:最大线程数:表示线程池中允许的最大线程数量。当队列满了并且活动线程数小于maximumPoolSize时,线程池会创建新的线程来处理任务。
- maximumPoolSize的值为Int.MAX_VALUE,即最大整数值,表示线程池可以创建任意数量的线程。
- 参数三:keepAliveTime:线程空闲时间:表示非核心线程的空闲时间超过该值时,线程会被销毁,直到线程池中的线程数等于corePoolSize。
- keepAliveTime的值为60,表示非核心线程的空闲时间为60秒。(你可以认为缓存是60秒)
- 参数四: unit:时间单位:表示keepAliveTime的单位。
- TimeUnit.SECONDS这里使用的是秒。
- 参数五:workQueue:任务队列:表示存储待执行任务的阻塞队列。
- SynchronousQueue,它是一个没有容量的阻塞队列,主要用于同步线程之间的数据交换。
- 参数六:threadFactory:线程工厂:用于创建新线程的工厂类。
- 自定义的threadFactory方法创建线程,并命名为"$okHttpName Dispatcher"。
总结:以上就是同步请求和异步请求发起的全部内容了。最终也是都通过链式的拦截器去发送了出去。那么就继续吧,看看拦截器都做了什么。
核心部分:责任链拦截器
顾名思义,拦截器,就是起到了一层层拦截的作用。也就是合格的过、不合格的留下。
这里拦截器也是用到了一种设计模式去设计,也就是责任链模式,他将多个拦截器进行链式的调用。
他将请求一层层分发下去,最后再将结果再一层层返回上来。
入口:getResponseWithInterceptorChain
通过上面的解析我们知道不管是同步还是异步最后都通过getResponseWithInterceptorChain()来获取结果。
那么我们第一步先来看看getResponseWithInterceptorChain作为拦截器的入口都有什么吧!
RealCall.kt
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// 保存所有拦截器的集合
val interceptors = mutableListOf<Interceptor>()
// 第一个可以说是自定义拦截器吧,做些日志之类的
interceptors += client.interceptors
// 重试重定向拦截器
interceptors += RetryAndFollowUpInterceptor(client)
// 桥接拦截器,补全网络请求
interceptors += BridgeInterceptor(client.cookieJar)
// 缓存拦截器,缓存结果的
interceptors += CacheInterceptor(client.cache)
// 链接拦截器,建立Socket连接,并缓存链接复用
interceptors += ConnectInterceptor
if (!forWebSocket) {
// 用户定义的网络拦截器,可以用来获取一些请求信息
interceptors += client.networkInterceptors
}
// 请求服务拦截器,与服务器进行请求,返回结果的
interceptors += CallServerInterceptor(forWebSocket)
// 1、责任链对象
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
var calledNoMoreExchanges = false
try {
// 2、调用RealInterceptorChain的proceed()方法
// 将请求向下一个连接器传递
val response = chain.proceed(originalRequest)
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
// 3、责任链的拦截器全部通过,并返回结果
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
总结以下几点内容:
- OkHttp默认是五个拦截器
- 可以进行自定义拦截器
- 通过RealInterceptorChain进行责任链的层次调用
重试重定向拦截器:RetryAndFollowUpInterceptor
主要内容:负责重试和请求重定向的一个拦截器
次要内容:创建了一个ExchangeFinder给ConnectInterceptor使用
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
先来看一下Interceptor接口:可以看到就一个方法intercept并且提供给我们的五大拦截器去实现。
因此,五大拦截器的主要内容、核心内容也都在intercept中被实现。后面也是主讲intercept方法中的内容。
RetryAndFollowUpInterceptor.kt
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
companion object {
//最大重定向次数
private const val MAX_FOLLOW_UPS = 20
}
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
....
....
while (true) {
// 1、创建了ExchangeFinder对象,给后续的链接拦截器使用
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
// 请求结果
var response: Response
var closeActiveExchange = true
try {
.....
try {
// 2.1、正常情况:下调用下一个拦截器,即 BridgeInterceptor
// 2.2、请求错误的情况:捕获然后重试重定向
response = realChain.proceed(request)
newExchangeFinder = true
} catch (e: RouteException) {
// 2.3、路线异常:检查是否重试 并进行重试
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
continue
} catch (e: IOException) {
// 2.4、IO异常: 检查是否重试 并进行重试
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newExchangeFinder = false
continue
}
// 3、请求的结果(没被try catch 拦截说明当前请求成功了,但是可能会被重定向)
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
// 4、 根据返回的response来判断需不需要重定向
val exchange = call.interceptorScopedExchange
val followUp = followUpRequest(response, exchange)
// 5、followUp为空,代表没有重定向,直接返回结果response
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
// 6、如果RequestBody有值且只许被调用一次,
// 也就是不许重定向,那么就直接返回response,别墨迹!!
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}
response.body?.closeQuietly()
//7、超过重定向最大次数抛出异常(20次)
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
//8、将新的请求赋值给request,继续循环,也就是重试了
request = followUp
priorResponse = response
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
网络桥接拦截器:BridgeInterceptor
主要内容:用来补全请求头的内容的,比如:(只列举部分,详细的大家自己去看一下Http相关知识)
Content-Type:用于指定请求体的类型
Content-Length:用于指定响应体的长度,表示我这次请求需要返回多少字节的内容,多用于分块传输;
Host:用于指定请求的目标主机,比如www.baidu.com。
User-Agent:用于向服务端表明身份信息,表示我是来自手机客户端的请求,还是来自某个浏览器的请求;
Cookie:用于在请求中携带之前存储的cookie数据,以识别客户端并提供个性化服务。
Content-Encoding:服务器告诉客户端自己使用了什么压缩方法,如gzip,deflate等;
如果响应头中有Content-Encoding: gzip,则会用 GzipSource 进行解析。
接下来往下看看,网络拦截器中内容
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
BridgeInterceptor.kt
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//1、创建请求对象,为了后面的内容构建
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
//2、补全请求头(if判断就省略了)
requestBuilder.header("Content-Type", contentType.toString())
requestBuilder.header("Content-Length", contentLength.toString())
requestBuilder.header("Host", userRequest.url.toHostHeader())
requestBuilder.header("Connection", "Keep-Alive")
requestBuilder.header("Accept-Encoding", "gzip")
requestBuilder.header("Cookie", cookieHeader(cookies))
//3、交给下一个拦截器继续操作
val networkResponse = chain.proceed(requestBuilder.build())
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
//4、如果Content-Encoding: gzip。就西药gzip解析。
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
.....
val gzipSource = GzipSource(responseBody.source())
....
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
总结:桥接拦截器就是这么简单了,就是在请求之前,补充好请求头而已。之后根据Content-Encoding来确定是否需要gzip解压。
缓存拦截器:CacheInterceptor
主要内容:众所周知OkHttp是有一套缓存机制的,CacheInterceptor就是负责读取缓存以及更新缓存的。内部是用OKio来实现读写的,所以大家在导入OkHttp时,会发现他会自动的导入OKio。
HTTP的缓存规则
- 强缓存:强缓存是在客户端(浏览器)中进行的缓存策略,服务端通过设置HTTP响应头中的Cache-Control和Expires字段来控制。
- Cache-Control字段:可以设置多个指令,如max-age、no-cache、no-store等。其中max-age代表缓存的最长有效时间(秒),当请求再次发生时,如果缓存未过期,则直接从缓存中获取数据。
- 举例:如果服务器设置了Cache-Control: max-age=3600,意味着资源在一小时内都是有效的。客户端再次请求相同资源时,可以直接从缓存中读取而不需要再次向服务器发起请求。
- 协商缓存:协商缓存是在服务器端进行的缓存策略,服务端通过设置HTTP响应头中的ETag和Last-Modified字段来控制。
- 举例:当客户端再次请求某个资源时,携带上次服务器返回的ETag和Last-Modified字段值,如果服务器判断资源未发生变化,则返回304状态码,告知客户端可以使用缓存的资源。
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
CacheInterceptor.kt
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
......
// 1、获取缓存策略;
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
// 1.1、网络请求对象(说明要网络请求服务器)
val networkRequest = strategy.networkRequest
// 1.2、缓存请求对象(缓存获取,有本地还有code = 304网路中的)
val cacheResponse = strategy.cacheResponse
.........
.........
// 2、如果networkRequest 和 cacheResponse 都是null
// 说明不想网络请求,又没有本地缓存。直接进行错误返回504
if (networkRequest == null && cacheResponse == null) {
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build().also {
listener.satisfactionFailure(call, it)
}
}
// 3、不请求网络、直接返回本地缓存
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
if (cacheResponse != null) {
listener.cacheConditionalHit(call, cacheResponse)
} else if (cache != null) {
listener.cacheMiss(call)
}
var networkResponse: Response? = null
try {
// 4、说明需要继续进行请求,来确定是协商缓存还是重新去服务器上获取资源
// 也就是继续调用下一个拦截器,链接拦截器
networkResponse = chain.proceed(networkRequest)
} finally {
if (networkResponse == null && cacheCandidate != null) {
cacheCandidate.body?.closeQuietly()
}
}
// 5、协商缓存
if (cacheResponse != null) {
// 5.1、并且服务器放回给我们304响应码,直接使用协商缓存中的缓存
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
val response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers, networkResponse.headers))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis)
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
networkResponse.body!!.close()
cache!!.trackConditionalCacheHit()
cache.update(cacheResponse, response)
return response.also {
listener.cacheHit(call, it)
}
} else {
cacheResponse.body?.closeQuietly()
}
}
// 6、到这里,说明缓存行不通了,是新的请求的资源
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
// 7、将本次最新得到的响应存到cache中去
if (cache != null) {
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
val cacheRequest = cache.put(response)
// 8、用OKio来进行缓存的写入
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
listener.cacheMiss(call)
}
}
}
// 8、让返回结果返回上一层拦截器
return response
}
总结:那就总结一下流程吧:
- 1、获取缓存策略
- 2、根据策略,不使用网络,又没有缓存的直接报错,并返回错误码504。
- 3、根据策略,不使用网络,有缓存的直接返回。
- 4、前面两个都没有返回,继续执行下一个Interceptor,即ConnectInterceptor。
- 5、接收到网络结果,如果响应code式304,则使用协商缓存,返回缓存结果。
- 6、读取新请求的网络结果
- 7、对新请求的数据内容进行缓存
- 8、返回数据结果给上层拦截器
链接拦截器:ConnectInterceptor
主要内容:
- 负责建立连接工作:封装了socket连接和TLS握手等逻辑
- 连接复用的工作:它会从连接池中取出符合条件的连接,以免重复创建,从而提升请求效率。
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
// 1、建立连接工作
val exchange = realChain.call.initExchange(chain)
// 2、copyexchange对象,到下一拦截器
val connectedChain = realChain.copy(exchange = exchange)
//3、调用下一拦截器
return connectedChain.proceed(realChain.request)
}
}
完事了!!!
呸!想得美,主要内容都在initExchange(chain)建立连接中。
internal fun initExchange(chain: RealInterceptorChain): Exchange {
synchronized(this) {
check(expectMoreExchanges) {
"released" }
check(!responseBodyOpen)
check(!requestBodyOpen)
}
// 1、这是前面重试重定向拦截器,创建的exchangeFinder(一个连接复用的缓存对象)
val exchangeFinder = this.exchangeFinder!!
// 2、查找连接,返回的是ExchangeCodec对象
val codec = exchangeFinder.find(client, chain)
// 3、创建Exchange用来发送和接受请求
val result = Exchange(this, eventListener, exchangeFinder, codec)
.....
.....
return result
}
我们从exchangeFinder.find点进去,一路点到findConnection是他连接的主方法。
@Throws(IOException::class)
private fun findConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean
):RealConnection {
if (call.isCanceled()) throw IOException("Canceled")
// 1、尝试重用连接
val callConnection = call.connection
if (callConnection != null) {
...
}
// 2、尝试从池中获取连接
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
...
}
...
// 3、创建一个新的连接(也就是上面没有获取到合适的链接去复用)
val newConnection = RealConnection(connectionPool, route)
call.connectionToCancel = newConnection
try {
//4、连接服务器 (创建Socket对象、TCP握手和TSL握手)
newConnection.connect(
connectTimeout,
readTimeout,
writeTimeout,
pingIntervalMillis,
connectionRetryEnabled,
call,
eventListener
)
} finally {
call.connectionToCancel = null
}
...
synchronized(newConnection) {
//5、添加到连接池中
connectionPool.put(newConnection)
call.acquireConnectionNoEvents(newConnection)
}
return newConnection
}
先总结一下流程:
- 1、尝试重用连接
- 2、尝试从池中获取连接
- 3、创建一个新的连接:(也就是上面没有获取到合适的链接去复用)
- 4、连接服务器:通过创建Socket对象、TCP握手和TSL握手也、使用OKio中的接口获得输入/输出流。
- 5、添加到连接池中
这里面有两个比较重要的内容,那就是连接的具体内容、还有连接的线程池了
连接池的内容过多给大家总结一下:
连接池:
- 创建添加:连接池默认的空闲连接数是5个,空闲的连接时间也是5分钟,如果空闲连接数超出了5个,那么就清理掉最近最久未使用的连接数,直到连接池中的连接数小于等于5个。(是不是想起了LRU缓存)
- 寻找复用:每次去复用连接池,需要去遍历比较当前连接的 Host, url, ip 等是否完全一样,满足条件就取出这个连接。
- 清除清理:另外对连接进行缓存时,如果当前的连接数超过了5个,那么会将最近最久未使用的连接进行清除。cleanup() 方法会每过一个清理周期自动做一次清理工作。
那也简单的来看一下连接吧:
RealConnection.connectSocket(连接)
@Throws(IOException::class)
private fun connectSocket(
connectTimeout: Int,
readTimeout: Int,
call: Call,
eventListener: EventListener
) {
...
//1、创建Socket对象
val rawSocket = when (proxy.type()) {
Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!
else -> Socket(proxy)
}
this.rawSocket = rawSocket
try {
//2、进行Socket连接
Platform.get().connectSocket(rawSocket, route.socketAddress, connectTimeout)
} catch (e:ConnectException) {
throw ConnectException("Failed to connect to ${route.socketAddress}").apply {
initCause(e)
}
}
try {
//3、okio中的接口,用来输入,类似于 InputStream
source = rawSocket.source().buffer()
//4、okio中的接口 ,用来输出,类似于OutputStream
sink = rawSocket.sink().buffer()
} catch (npe: NullPointerException) {
if (npe.message == NPE_THROW_WITH_NULL) {
throw IOException(npe)
}
}
}
可以看到,是通过创建Socket对象使用OKio中的接口获得输入/输出流。
总结:ConnectInterceptor的内容就是这些了,可以看得出来ConnectInterceptor是一个特别重要的拦截器,在这个拦截器中真正的建立了连接,并且获得了输入输出流,为将来的输入输出进行了准备。
内容过程如下:
- 1、首先查找是否有可用的连接:先去缓存池中找,没有的话重新创建
- 2、建立链接:创建Socket对象、TCP握手和TSL握手也、使用OKio中的接口获得输入/输出流。
- 3、加入链接到连接池
- 4、返回Exchange对象给下一个对象
请求服务拦截器:CallServerInterceptor
终于到了最后一个拦截器:服务请求拦截器。
上面已经通过链接拦截器,建立起了客户端和服务端的联系。
那么接下来就是和服务器发送header和body以及接收服务器返回的数据了,这些逻辑都在这个拦截器中。
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
直接来看,请求服务拦截器中的intercept中都做了些什么吧(主要内容都是用OKio做的,所以直接简略的看主要部分吧)
class CallServerInterceptor(private val forWebSocket: Boolean) :Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val exchange = realChain.exchange!!
val request = realChain.request
val requestBody = request.body
// 1、将请求头写入到socket中,如果是GET那么请求已经结束
exchange.writeRequestHeaders(request)
var responseBuilder: Response.Builder? = null
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
// 2、在HTTP中POST请求会发送两个包,
// 先发送请求头,获得相应为100后再发送请求体。
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
exchange.flushRequest()
responseBuilder = exchange.readResponseHeaders(expectContinue = true)
exchange.responseHeadersStart()
}
// 3、写入请求体 通过Okio将body写入到socket中,用于发送给服务器
requestBody.writeTo(bufferedRequestBody)
// 4、读取响应头(通过ExchangeCodec协议类)
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
var response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
var code = response.code
response = if (forWebSocket && code == 101) {
...
} else {
// 5、读取响应体(通过ExchangeCodec协议类)
response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
}
...
return response
}
}
了解HTTP的同学们,应该很容易就知道CallServerInterceptor是在干什么了,就是在做客户端与服务端的请求的数据的交互而已。
总结:
- 1、先写入请求头,如果是GET请求的话就已经请求完毕,POST请求的话是先发送请求头再发送请求体,会发送两个TCP包
- 2、然后读取响应头、读取响应体。利用ExchangeCodec协议类
- 3、最终将响应的结果返回,经过层层拦截器。
拦截器总结
作为重中之重的拦截器,采用了责任链的模式,层层的请求,又层层的将结果返回回去。
我们用一个流程图来简述这5个拦截器吧:(说句实话,如果真是认真看了,并且对照了真正的源码去走流程,根本不需要流程图去辅助,真的通透,因为OkHttp本身设计就很通透)
整两个小问题聊一聊
Application Interceptors与Network Interceptors
还几个拦截器的入口吗?
Application Interceptors在我们的第一个拦截器中
Network Interceptors在我们的倒数第二个拦截器中
internal fun getResponseWithInterceptorChain(): Response {
// 保存所有拦截器的集合
val interceptors = mutableListOf<Interceptor>()
// 第一个可以说是自定义拦截器吧,做些日志之类的
interceptors += client.interceptors
// 重试重定向拦截器
interceptors += RetryAndFollowUpInterceptor(client)
// 桥接拦截器,补全网络请求
interceptors += BridgeInterceptor(client.cookieJar)
// 缓存拦截器,缓存结果的
interceptors += CacheInterceptor(client.cache)
// 链接拦截器,建立Socket连接,并缓存链接复用
interceptors += ConnectInterceptor
if (!forWebSocket) {
// 用户定义的网络拦截器,可以用来获取一些请求信息
interceptors += client.networkInterceptors
}
// 请求服务拦截器,与服务器进行请求,返回结果的
interceptors += CallServerInterceptor(forWebSocket)
不同点:他俩的主要区别是他的拦截的时机不同,在项目中使用也是看他的拦截时机,去确定使用哪个拦截器进行自定义。
相同点:都提供给我们进行自定义使用
- Application Interceptors
- 发生时机:请求发送前和网络响应后
- 特点:不需要担心中间过程的响应,如重定向和重试
- 调用次数:只被调用一次
- Network Interceptors
- 发生时机:在与服务器进行交互的过程中
- 特点:能够操作中间过程的响应,如重定向和重试
- 调用次数:可能被调用多次,因为有重定向和重试的情况
怎样自定义拦截器,用来干嘛
先来定义一个拦截器(先来一个Application Interceptors的)
我们可以利用Application Interceptors去做一些对整个请求过程的日志
需求:记录一下整个请求的时间
class MyInterceptor : Interceptor {
private val TAG = "MyInterceptor"
override fun intercept(chain: Interceptor.Chain): Response {
// 1、获得此次请求的request,拿到以后可以根据需求进行自定义
val request = chain.request()
// 开始时间
val prevCallTime = System.nanoTime()
// 2、将任务交给下一个拦截器
val response = chain.proceed(request)
// response 返回时间
val laterCallTime = System.nanoTime()
// 打印本次响应结果返回到本层拦截器的时间
Log.v(TAG, "consume time: ${laterCallTime - prevCallTime}")
// 3、返回此次请求的结果
return response
}
}
其中我们需要注意的点:
- 继承Interceptor ,重写intercept方法
- 获取request ,通过chain.proceed责任链去将请求送给下一个拦截器处理,也就是重试重定向拦截器。
- 返回此次请求的结果response
在自定义拦截器中我们监听本次请求的时间。
怎么使用呢?
只需要在构建 OkHttpClient 的时候将拦截器添加进去就可以了。
val client = OkHttpClient().newBuilder().apply {
.....
.....
// Application拦截器
addInterceptor(MyInterceptor())
....
}.build()
再演示一下利用Network Interceptors的自定义拦截器使用
需求:我们通过拦截器去打印一下重定向的次数和重定向的URL
class CustomInterceptor : Interceptor {
private val TAG = "MyInterceptor"
override fun intercept(chain: Chain): Response {
// 1、获取当前的request
val request: Request = chain.request()
// 2、执行request,获取response
val response: Response = chain.proceed(request)
// 获取重定向的次数
val redirectCount =
if (response.networkResponse() != null) response.networkResponse()
.priorResponseCount() else 0
// 打印重定向的URL和次数
Log.v(TAG, "URL: ${request.url}")
Log.v(TAG, "Redirect count: ${redirectCount}")
//3、返回此次请求的结果
return response
}
}
需要注意的和上面一样都是一个套路。
怎么去使用呢?
val client = OkHttpClient().newBuilder().apply {
.....
.....
// Network拦截器
addNetworkInterceptor(MyInterceptor())
....
}.build()
总结
OkHttp的源码呢,相对Glide来说真是舒服多了,一是分支少,二是流程不拐弯。
这都得力于他的责任链、构建者、适配器等设计模式。让整个源码看着十分的舒服。
加油兄弟们!!!!