通常的使用方法:
val okHttpClient : OkHttpClient = OkHttpClient.Builder()
.connectTimeout(...) // 可设置连接、读写、调用等超时
.dispatcher(...) // 可设置自定义的调度器
.connectionPool(...) // 可设置自定义连接池
.cache(...) // 可设置缓存
.addInterceptors(...) // 可向责任链添加自定义拦截器等等
.build()
val request : Request = Request.Builder()
.url(...)// 设置url
.headers(...)// 设置请求头
.get() // 设置请求方法
.build()
// 发起一个异步请求,也可使用execute在工作线程中发起同步请求
okHttpClient.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
}
override fun onFailure(call: Call, e: IOException) {
}
})
OkHttpClient的实例化过程和构造器模式
在OkHttpClient的使用过程中,首先采用构造器对其进行实例化,使用构造器的无参构造函数,将默认使用分发器、连接池、协议版本支持HTTP1.1/HTTP2、连接读写超时时间均设为10000毫秒、不使用代理、不使用Cookies、无缓存(方便根据服务器的策略使用缓存)等配置。
Request和Call的实例化过程
类似地,Request也是使用构造器模式进行实例化,可以对url、请求头、请求方法进行配置;
然后使用OkHttpClient的实例方法newCall,传入Request实例,创建RealCall对象;
发起同步execute和异步enqueue请求
同步和异步方法都是RealCall的实例方法,在创建RealCall后调用,可发起请求:
他们的差别是:同步没有工作线程,需手动在线程中执行,异步在工作线程中执行,并且默认被两个队列维护。
3.1 同步请求
// RealCall.kt
override fun execute(): Response {
// 同步代码块,确保一个线程在执行请求时被阻塞;
// 开始计算超时
// 事件监听器启动监听(监听dns、代理、https的安全连接、网络连接等的启动终止等)
...
try {
// 1. 分发器将请求添加到同步调用队列
client.dispatcher.executed(this)
// 2. 使用责任链完成任务并返回响应
return getResponseWithInterceptorChain()
} finally {
// 3. 结束分发,移除请求
client.dispatcher.finished(this)
}
}
其中executed方法就是将RealCall实例,添加到一个双头队列中:
private val runningSyncCalls = ArrayDeque
finished 则会执行响应的移除操作;
3.2 异步请求
// RealCall.enqueue --> Dispatcher.enqueue
// Dispatcher.kt
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
// 1. 向ready的异步调用队列中添加call;
readyAsyncCalls.add(call)
// 2. 复用相同HOST的调用计数,为了方便统计同HOST的连接数(@Volatile var callsPerHost)
// 这是一个多线程共享数据,与连接池有关,连接池要求同一个HOST的连接数不得超过5,
// 最多不得超过64,否则会放入队列阻塞
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
// 3. 异步请求的重点方法
promoteAndExecute()
}
异步请求,触发RealCall的enqueue方法,执行了检查当前线程是否正在执行和启动事件监听后,触发了Dispatcher的enqueue方法。
最后进入了核心方法Dispatcher#promoteAndExecute()
// Dispatcher.kt
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
// 1. 同步遍历ready的异步请求队列
// 如果running的异步请求队列超过了最大长度64,则当前的请求不会被执行;
// 如果running的异步请求队列相同HOST的请求数超过了5,则继续循环只执行同HOST未超过5的请求;
// 满足要求的请求可以被执行,将会被添加到executableCalls列表中
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
i.remove()
asyncCall.callsPerHost.incrementAndGet()
executableCalls.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
// RealCall$AsyncCall
// 2. 遍历可执行的请求列表,并使用线程池执行请求。
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
fun executeOn(executorService: ExecutorService) {
...
// 3. 触发了线程池执行Runnable.run()
executorService.execute(this)
...
}
override fun run() {
...
// 4. RealCall$AsyncCall实现了Runnable,最终执行了责任链方法。
val response = getResponseWithInterceptorChain()
...
responseCallback.onResponse(this@RealCall, response)
...
responseCallback.onFailure(this@RealCall, canceledException)
...
client.dispatcher.finished(this)
}
总结:
分析得到,同步方法执行请求,由一个runningSyncCall的队列维护,直接在调用线程中执行责任链发起请求;
异步方法执行请求,则是由readyAsyncCall、runningAsyncCall两个队列维护,一个存放准备好需要请求的任务,一个存放正在执行的请求任务;并且不允许连接数大于64、同HOST连接数大于5,否则请求会阻塞在ready队列中;可执行的请求任务将最终通过执行责任链完成,并按需触发onResponse、onFailure方法。
1. 什么是责任链模式呢?
一个列表在执行任务的时候,上一个任务都持有下一个任务的引用。在上一个任务执行方法中完成自己的任务后,用下一个任务的引用触发执行下一个任务的方法。以此不断执行下去,最终完成所有任务的执行。
在一个业务(例如网络请求)由多个任务(一个任务集合:超时重连、请求桥接、缓存使用和数据更新、连接建立、数据传输等)组成,并不知晓具体会执行其中的哪个任务(集合的元素),或者哪些任务(集合的子集)时,使用责任链模式,让使用业务的发送者无需关心具体执行的过程,由责任链(接收者)负责具体确定使用那个或哪些任务,最终得到发送者关心的结果。实现了发送者与接收者之间的解耦
2. OkHttp怎样使用的责任链
如下简化代码:
首先是有一个Interceptor接口,其intercept(Chain)方法由proceed(Request)方法触发,是实现业务逻辑和使用proceed调用责任链的下一个任务,并最终返回Responce给第一个proceed(Request)方法;
另外再有一个Chain接口,RealInterceptorChain继承了此接口,实现了接口的proceed(Request)方法,取出列表中的Interceptor、构造index+1的Chain,然后执行intercept(Chain)方法。
类似一种递归调用的方式,遍历一个拦截器列表。
// Interceptor.kt
interface Interceptor {
@Throws(IOException::class)
fun intercept(chain: Chain): Response
...
interface Chain {
@Throws(IOException::class)
fun proceed(request: Request): Response
...
}
}
// RealInterceptorChain.kt
class RealInterceptorChain(
...
) : Interceptor.Chain {
@Throws(IOException::class)
override fun proceed(request: Request): Response {
...
val next = copy(index = index + 1, request = request)
val interceptor = interceptors[index]
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
...
return response
}
}
重试和重定向拦截器:
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
// 1. 转成RealInterceptorChain对象;
val realChain = chain as RealInterceptorChain
var request = chain.request
val call = realChain.call
...
while (true) {
// 2. 创建ExchangeFinder对象,该对象用来从连接池中获取可复用的连接;
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
...
try {
...
try {
// 3. 进入责任链的下一个拦截器执行
response = realChain.proceed(request)
newExchangeFinder = true
} catch (e: RouteException) {
// 4. 当与服务器建立连接失败时,如果是可恢复的(根据recover方法判断),
// 则回复并尝试重新建立连接————重试
...
continue
}
...
val exchange = call.interceptorScopedExchange
// 5. 根据返回的response的状态码(如300/301/302/303/307/308/401/407/408/421/503等)
// 等条件确定是否需要重定向,不需要重定向会返回空
val followUp = followUpRequest(response, exchange)
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}
response.body?.closeQuietly()
// 重定向的次数不能超过最大限制否则抛出异常
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
request = followUp
priorResponse = response
} finally {
// 6. ExchangeFinder置空和退出连接
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
...
}
桥接拦截器的作用是:为请求封装请求头和对返回的响应体进行GZIP解压缩。
可以看到interceptor方法中,根据需求配置Request Header的Content-Type、Content-Length、Host、Transfer-Encoding、Connection、Cookies、User-Agent等字段;
并且在返回的response,进行判断是否使用GZIP压缩数据,然后决定是否解压缩;
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
val body = userRequest.body
if (body != null) {
val contentType = body.contentType()
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString())
}
val contentLength = body.contentLength()
if (contentLength != -1L) {
requestBuilder.header("Content-Length", contentLength.toString())
requestBuilder.removeHeader("Transfer-Encoding")
} else {
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("Content-Length")
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", userRequest.url.toHostHeader())
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive")
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true
requestBuilder.header("Accept-Encoding", "gzip")
}
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies))
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", userAgent)
}
val networkResponse = chain.proceed(requestBuilder.build())
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
...
}
Cache拦截器是根据本地缓存的策略和数据决定是否使用缓存或者、是否请求验证资源是否过期、是否请求获取数据以及跟新缓存策略和数据的处理器。
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
// 1. 根据Request获取缓存中的Response,cache实质是使用一个DisLruCache来实现的缓存,
// 这个缓存使用Request的url作为Key,根据Key获取Entry进而得到Response
val cacheCandidate = cache?.get(chain.request())
// 2. 根据缓存、请求获取缓存策略。
val now = System.currentTimeMillis()
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
...
// 3.当网路不可用、缓存无效时,返回503响应
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)
}
}
// 4. 当缓存可用,直接使用缓存返回响应
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
...
// 6. 继续执行网络请求(缓存过期、或缓存策略要求先验证响应新鲜程度、或要求重新请求等情况下)
networkResponse = chain.proceed(networkRequest)
// 7. 执行验证响应新鲜程度,得到的响应码是304(HTTP_NOT_MODIFIED)时,直接使用缓存,并更新本地缓存;
if (cacheResponse != null) {
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()
...
cache.update(cacheResponse, response)
return response.also {
listener.cacheHit(call, it)
}
} else {
cacheResponse.body?.closeQuietly()
}
}
// 8. 执行网络请求(响应新鲜程度不满足要求,即服务端响应已经修改)后,
// 根据缓存策略将新的响应数据和策略存储到缓存中
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
if (cache != null) {
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
listener.cacheMiss(call)
}
}
}
...
}
return response
}
...
}
继续追踪CacheStrategy分析缓存策略的处理:
CacheStragtegy 缓存策略对象,有两个重要成员变量。
详见注释:
// CacheStrategy.kt
fun compute(): CacheStrategy {
// 1. 进入核心方法
val candidate = computeCandidate()
// 2. 当Request不为空,并且Cache-Control:Only-If-Cache的策略时,返回两个null的CacheStrategy
if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
return CacheStrategy(null, null)
}
return candidate
}
private fun computeCandidate(): CacheStrategy {
// 1.1 没有缓存时,通常是首次请求或Cache-Control:no-store;
if (cacheResponse == null) {
// 返回networkRequest不为空,cacheResponse为空,将发起请求获取数据;
return CacheStrategy(request, null)
}
// 1.2 请求使用的HTTPS但是没有进行SSL/TLS握手;
if (request.isHttps && cacheResponse.handshake == null) {
// 返回networkRequest不为空,cacheResponse为空,将发起请求获取数据;
return CacheStrategy(request, null)
}
// 缓存策略发生变化,才会执行是否可缓存的检查
if (!isCacheable(cacheResponse, request)) {
return CacheStrategy(request, null)
}
// 1.3 Cache-Control:no-cache的情况,需要先请求服务器校验资源新鲜度,然后再决定是否使用缓存
val requestCaching = request.cacheControl
if (requestCaching.noCache || hasConditions(request)) {
return CacheStrategy(request, null)
}
// 1.4 下面就是根据Cache-Control(没有设置no-cahche)
// 的max-age、min-fresh、max-stale和cacheResponse的Age来
// 判断缓存是否过期,如果没有过期或者过期但处于仍然可用的状态下会继续使用缓存。
// (1) age + min_fresh <= max_age : 未过期;
// (2) (age + min_fresh > max_age) && (age + min_fresh <= max) :
// 过期仍可使用 会挂110警告码;
// 超过24小时,max-age=-1,未设置expires的情况会挂113警告码。
// (3) 过期,在networkRequest的头部添加
// ETag或If-None-Match或If-Modified-Since等字段,用于请求验证cacheResponse的新鲜度
val responseCaching = cacheResponse.cacheControl
val ageMillis = cacheResponseAge()
var freshMillis = computeFreshnessLifetime()
if (requestCaching.maxAgeSeconds != -1) {
freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
}
var minFreshMillis: Long = 0
if (requestCaching.minFreshSeconds != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
}
var maxStaleMillis: Long = 0
if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
}
if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
val builder = cacheResponse.newBuilder()
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
}
val oneDayMillis = 24 * 60 * 60 * 1000L
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"")
}
return CacheStrategy(null, builder.build())
}
val conditionName: String
val conditionValue: String?
when {
etag != null -> {
conditionName = "If-None-Match"
conditionValue = etag
}
lastModified != null -> {
conditionName = "If-Modified-Since"
conditionValue = lastModifiedString
}
servedDate != null -> {
conditionName = "If-Modified-Since"
conditionValue = servedDateString
}
else -> return CacheStrategy(request, null)
}
val conditionalRequestHeaders = request.headers.newBuilder()
conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)
val conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build()
return CacheStrategy(conditionalRequest, cacheResponse)
}
初始化了Exchange对象,这个对象是实际处理网络IO的对象,使用了连接池中可复用的连接和Codec,提供了一系列方法:如writeRequest、flush、readResponse等网络IO读写。
责任链中最后一个拦截器,用来使用Exchange,执行网络IO
分发器Dispatcher
1.1 分发器是在OkHttpClient构造时,初始化的;
1.2 分发器中实例化了一个60s 保活、名为okhttp3.Client Dispatcher的CacheThreadPool(0个核心线程,Integer.MAX_VALUE个非核心线程);
1.3 分发器是负责将同步/异步请求分发到责任链中执行的重要模块;
1.4 分发器维护了三个双头队列,用于添加删除同步异步Call;
1.5 提供了取消所有请求的方法;
线程池
在分发其中实例化的线程池用于异步请求;
连接池ConnectionPool
3.1 维护了5个空闲连接、保活5分钟;
3.2 由RealConnectionPool实现其功能:维护使用的连接、空闲连接、连接个数、清除连接、连接保活时间等;
3.3 在ConnectionInterceptor拦截器执行是使用:
ConnectionInterceptor.intercept --> RealCall.initExchange --> ExchangeFinder.find
--> ExchangeFinder.findHealthyConnection
最后通过将构造的Exchange传入下一个责任链CallServerInterceptor,在其中使用Exchange.HttpCodec,进行网络IO