目录
构建OkHttpClient请求工厂
OkHttpClient是用于请求的工厂,可用于发送HTTP请求并读取其响应。创建一个
OkHttpClient
实例并将其用于所有HTTP调用时,OkHttp的性能最佳,因为每个客户端都拥有自己的连接池和线程池,重用连接和线程可减少延迟并节省内存。 相反,为每个请求创建客户端都会浪费空闲池上的资源。
//方式1
client = OkHttpClient()
//方式2
client = OkHttpClient.Builder()
.addInterceptor(MyInterceptor())
.build()
方式1直接通过OkHttpClient构造函数,构造一个okHttpClient对象,这个对象各种属性全部都用默认构造的。并且因为所有属性val修饰,所以这些OkHttpClient实例一旦构造,其属性就不能修改。
如下述源码所示,所有属性均取自于builder内部类的默认值。
// Dispatcher 处理器
@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher
// 连接池
@get:JvmName("connectionPool") val connectionPool: ConnectionPool = builder.connectionPool
// 应用拦截器
@get:JvmName("interceptors") val interceptors: List<Interceptor> =
builder.interceptors.toImmutableList()
// 网络拦截器
@get:JvmName("networkInterceptors") val networkInterceptors: List<Interceptor> =
builder.networkInterceptors.toImmutableList()
...
方式2通过使用建造者模式创建实例,建造者模式的特点是将实例的创建与表示分离,将复杂的创建过程隐藏。
方式2可以分为两个步骤
步骤1: OkHttpClient.Builder() 与 .build()
调用OkHttpClient的Builder内部类
调用builder的内部类build方法返回OkHttpClient对象。
步骤2: .addInterceptor(MyInterceptor())
这里并不只是指调用这个方法,而是指在此时可以做组装client属性的组操作。这一步传入自定义的属性值,如添加一个拦截器
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()
internal var connectionPool: ConnectionPool = ConnectionPool()
...
/**
* Sets the dispatcher used to set policy and execute asynchronous requests. Must not be null.
*/
fun dispatcher(dispatcher: Dispatcher) = apply {
this.dispatcher = dispatcher
}
/**
* Sets the connection pool used to recycle HTTP and HTTPS connections.
*
* If unset, a new connection pool will be used.
*/
fun connectionPool(connectionPool: ConnectionPool) = apply {
this.connectionPool = connectionPool
}
...
}
fun build(): OkHttpClient = OkHttpClient(this)
创建Request实例配置请求参数
Request 实例同OkHttpClient支持建造者模式,相比较OkHttpClient来说,Request类的结构显得简单很多。
val request = Request.Builder()
.url("http://com.abc.com/get")
.post("foo".toRequestBody("text/plain".toMediaType()))
.build()
Request构建基相对于OkHttpClient使用的更加频繁,因为我们一次请求就需要一个独立的Request实例和一个OkHttpClient实例,OkHttpClient复用多次,而request每一处请求都需要配置一次。
主要也分为两步
步骤1:Request.Builder() 与 .build()
调用Request的Builder内部类
调用builder的内部类build方法返回Request对象。
步骤2:参数配置
添加访问url,添加请求方式post or get 等等方法,添加请求体requestBody,添加请求头headers
newCall开始执行网络请求
接着就是调用OkHttpClient的newCall方法返回一个Call对象,newCall是直接去new了一个RealCall对象,RealCall是Call接口类唯一的实现类。
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
Realcall是连接应用程序层和网络层之间的桥梁,传入一个OkHttpClient,一个request,还有一个boolean 的值。
Realcall中有几个非常重要的方法。
同步执行网络请求方法execute,异步请求enqueue,还有一个cancel方法。
同步请求
override fun execute(): Response {
// 检查是否重复调用
check(executed.compareAndSet(false, true)) { "Already Executed" }
//开始计时超时、发请求开始回调
timeout.enter()
// 详解1
callStart()
try {
// 详解2
client.dispatcher.executed(this)
// 详解3
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
详解1
这里的callstart并不是真正的开启请求,而是调用eventListener.callStart方法,而eventListener是一个抽象类,里面的方法都是抽象方法,HTTP请求的主要的时间节点都会回调eventListener中的方法。
详解2
client.dispatcher.executed(this)这里是将这个call添加到了请求runningAsyncCalls队列中,加到这个队列的目的是为了方便管理。
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
详解3
getResponseWithInterceptorChain()返回的是请求结果,这里在后边会详细讲解。
异步请求
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
// 详解1
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
详解1
AsyncCall是RealCall的内部类并且实现了Runnable接口,传入了参数callback。client.dispatcher.enqueue会将这个AsyncCall加入到readyAsyncCalls中,并且将这个任务提交到线程池。执行的任务如下:
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
...
try {
val response = getResponseWithInterceptorChain()
signalledCallback = true
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
...
responseCallback.onFailure(this@RealCall, e)
...
} catch (t: Throwable) {
cancel()
if (!signalledCallback) {
val canceledException = IOException("canceled due to $t")
canceledException.addSuppressed(t)
responseCallback.onFailure(this@RealCall, canceledException)
}
throw t
} finally {
client.dispatcher.finished(this)
}
}
}
第一步便是getResponseWithInterceptorChain(),并将请求结果通过onResponse回调给调用者,接着有一些异常情况处理将错误结果通过onFailure进行回调。
拦截器与责任链
@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)
// 添加连接拦截器
interceptors += ConnectInterceptor
if (!forWebSocket) {
// 添加网络拦截器
interceptors += client.networkInterceptors
}
// 添加请求拦截器
interceptors += CallServerInterceptor(forWebSocket)
// 构建责任链
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 {
// 执行责任链,返回response
val response = chain.proceed(originalRequest)
...
return response
}
...
}
拦截器 | 作用 |
---|---|
interceptor | 拿到的是原始请求,可以添加一些自定义header、通用参数、参数加密、网关接入等等。 |
RetryAndFollowUpInterceptor | 处理错误重试和重定向 |
BridgeInterceptor | 应用层和网络层的桥接拦截器,主要工作是为请求添加cookie、添加固定的header,比如Host、Content-Length、Content-Type、User-Agent等等,然后保存响应结果的cookie,如果响应使用gzip压缩过,则还需要进行解压。 |
CacheInterceptor | 缓存拦截器,如果命中缓存则不会发起网络请求。 |
ConnectInterceptor | 连接拦截器,内部会维护一个连接池,负责连接复用、创建连接(三次握手等等)、释放连接以及创建连接上的socket流。 |
networkInterceptors | 用户自定义拦截器,通常用于监控网络层的数据传输。 |
CallServerInterceptor | 请求拦截器,在前置准备工作完成后,真正发起了网络请求。 |
重试与重定向拦截器
重试与重定向拦截器在应用拦截器之后,作用如其名,当遇到异常情况和满足重试条件或者需要重定向是会重新执行后续操作。
RetryAndFollowUpInterceptor的整体逻辑写在一个while的无限循环中,分两种情况需要会重新执行。
(1)出现路由异常或者IO异常并且满足以下四个条件:支持失败重连机制、可以多次请求、异常并非致命、还有其他的尝试线路。
(2)需要重定向的判断条件是对照当前返回的response的code重新构建request,当request为null就是无需重定向,当request不为null且可以未超出最大重定向次数。
应用拦截器与网络拦截器
我们可以在构建okhttpClient的时候添加应用拦截器和网络拦截器,这俩拦截器需要我们通过继承Interceptor并重写intercept方法实现。如下是应用拦截器直接获取到了chain的request,然后对request添加Header参数。
class MyInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
var headers = Headers.Builder()
headers.add("key", "value")
headers.build()
request.headers(headers)
return chain.proceed(request)
}
}
那么应用拦截器与网络拦截器有什么区别呢?
应用拦截器在重定向拦截器前执行,网络拦截器在重定向拦截器后执行。当重定向拦截器出现异常或者重定向导致多次执行时会导致其后边的拦截器跟着一起多次执行,而在重定向拦截器前执行的应用拦截器只会执行一次。应用拦截器执行一次表示客户端请求一次,网络拦截器执行一次表示发起一次网络请求。
桥接拦截器
桥接拦截器的作用是连接应用层代码和网络层代码。它首先会通过用户的请求构建一个网络请求,再执行这个网络请求,最后通过网络返回的应答转换为一个用户方便处理的应答。
(1)转换用户的请求为网络请求,即在请求头部添加Content-Type、Content-Length、Host、Connection、Accept-Encoding、Cookie、User-Agent参数
(2)转换网络请求应答为用户请求应答,并在接收到内容后进行解压,省去了应用层处理数据解压的麻烦,移除应答头的Content-Encoding、Content-Length。
缓存拦截器
缓存拦截器的内部逻辑可以概括为两个作用,第一个是使用缓存,第二个是更新缓存。
(1)缓存的使用与否是根据缓存策略判定的,而缓存策略是根据当前时间戳、request与该request的缓存response生成的。详细的缓存策略这里不多讲,最终我们会得到networkRequest用于判断是否需要访问网络,以及cacheResponse用于判断是否有对应的本地缓存。
(2)当满足一些条件时会更新和保存缓存,只有GET请求才会保存缓存,不然会丢弃缓存。
连接拦截器
连接拦截器的作用是负责dns解析、socket连接、tsl连接。ConnectInterceptor这个类本身比较简单,核心流程由下面这行关键代码进入
val exchange = realChain.call.initExchange(chain)
粗略的建连过程如上时序图所示,最开始由ConnectInterceptor发起初始化Exchange,initExchange的作用是找一个连接去运载即将进行的请求和响应。exchangeFinder去执行find方法找连接的,exchangeFinder是在RetryAndFollowUpInterceptor循环执行的第一步就被初始化的。而这个find过程又是一个比较关键的部分了,可以用如下流程图表示基本流程。
dns寻址
dns寻址在socket建立连接之前,目的是将请求的域名转换为ip。dns的关键步骤在ExchangeFinder中的这行代码,其主要逻辑在RouteSelector这个类里。
val localRouteSelection = localRouteSelector.next()
在RouteSelector中的调用链简单来说可以分为以上四步,如果在构建okhttpclient的时候没有指定dns那么这里会调用默认的DnsSystem去完成dns解析。
socket连接
创建socket连接的过程
请求服务拦截器
连接拦截器已经完成了socket连接的建立,请求服务拦截器的主要作用是发送请求头和请求体了。请求服务拦截器执行了以下几个动作:
(1)写入请求Header
(2)满足发送body的条件就向服务器发送请求body
(3)得到响应头response header
(4)有响应体就构建响应体
(5)返回响应
但是真正IO是由ExchangeCodec的子类Http1ExchangeCodec或Http2ExchangeCodec去实现的,而他俩的底层逻辑则是由okio的BufferedSink实现的。
整体架构
到此okhttp已解析完成,整体架构如下图(图摘自
oppo技术
)
总结
此文详细的讲解了okhttp从构建请求到完成请求的完整过程,包括同步与异步两种调用方式及7个拦截器的工作流程。