使用流程
我們都知道一個簡單的OkHttp請求流程是這么寫的:
val url = "http://www.baidu.com/"
//1.新建OKHttpClient客戶端
val okHttpClient = OkHttpClient()
//新建一個Request對象
val request = Request.Builder()
.url(url)
.build()
//2.Response為OKHttp中的響應
okHttpClient.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {}
override fun onResponse(call: Call, response: Response) {}
})
在使用OkHttp發(fā)起一次請求時,對于使用者最少存在OkHttpClient、Request與Call三個角色逢勾,其中OkHttpClient和Request的創(chuàng)建可以通過它提供的Builder(建造者模式)。而Call則是把Request交給OkHttpClient之后返回的一個已準備好執(zhí)行的請求。
但是它的實際請求流程是這樣的:
發(fā)現(xiàn)調(diào)用execute或者enqueue之后會去到dispatcher(分發(fā)器)再到interceptors(攔截器),這兩個是什么呢纱烘?后面會講到
先來看看execute或者enqueue調(diào)用后做了什么事。點擊execute或者enqueue發(fā)現(xiàn)是接口方法,那就找它的實現(xiàn)體山宾,看看newCall()
//OkHttpClient.kt
override fun newCall(request: Request): Call {
return RealCall.newRealCall(this, request, forWebSocket = false)
}
再點RealCall.newRealCall(),發(fā)現(xiàn)當OkHttpClient調(diào)用newCall時其實調(diào)用的是RealCall.newRealCall()翎猛,所以具體實現(xiàn)都在RealCall里面。
假如調(diào)用異步請求enqueue是這樣的:okHttpClient.newCall(request).enqueue() -> RealCall.newRealCall().enqueue()
//RealCall.kt
override fun enqueue(responseCallback: Callback) {
//1
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
//2
transmitter.callStart()
//3
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
分析:
- 1:首先用判斷這個Call是否被調(diào)用,如果沒有則executed = true遗座,繼續(xù)往下走;如果被調(diào)用了則會拋異常。
- 2:用于監(jiān)聽請求的信息宽堆,包括DNS的解析開始和結束時間,連接的開始和結束時間。
- 3:重點在這里,調(diào)用了OkHttpClient的dispatcher的enqueue方法,先不看AsyncCall是什么驾茴,其實到這里就是:OkHttpClient.newCall().enqueue() -> RealCall.newCall().enqueue() -> Dispatcher.enqueue()
而Dispatcher是一個分發(fā)器,至于為什么被叫分發(fā)器击碗,看完下面的解析你就知道了。
分發(fā)器Dispatcher
在Dispatcher中這么兩個變量、三個雙向隊列和一個線程池:
//Dispatcher.kt
//異步請求同時存在的最大請求
var maxRequests = 64
//異步請求同一域名同時存在的最大請求
var maxRequestsPerHost = 5
/** Ready async calls in the order they'll be run. */
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private val runningSyncCalls = ArrayDeque<RealCall>()
//SynchronousQueue的線程池
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("OkHttp Dispatcher", false))
}
return executorServiceOrNull!!
}
第一個隊列是在異步請求中,用于存儲等待執(zhí)行Call的隊列猖吴;第二個是在異步請求中绑谣,存儲正在執(zhí)行Call的隊列幌衣;第三個則是同步請求中,存儲正在執(zhí)行Call的隊列楚里。至于在異步請求中,當前Call是去到等待隊列還是執(zhí)行隊列,取決于maxRequests和maxRequestsPerHost。
maxRequests 是當前最多同時的請求個數(shù)满葛,maxRequestsPerHost 是當前主機最多的請求個數(shù)。
executorService線程池則用SynchronousQueue存儲,主要是避免任務假如成功,導致任務等待阻塞,所以SynchronousQueue讓其加入隊列失敿ぢ省(SynchronousQueue是沒有容量的隊列)低缩,直接新建新的線程執(zhí)行該任務
剛才說到最終會來到Dispatcher.enqueue()
//Dispatcher.kt
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call)
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!call.get().forWebSocket) {
val existingCall = findExistingCallWithHost(call.host())
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
首先會去把這個Call加入到等待隊列中讳推,然后直接走promoteAndExecute()
promoteAndExecute()的作用是處理對等待隊列中的Call,簡單點來說就是等待隊列為空,則不作處理镰吆;若不為空摧找,根據(jù)情況分析:
//Dispatcher.kt
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
//1
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
//2
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
if (asyncCall.callsPerHost().get() >= this.maxRequestsPerHost) continue // Host max capacity.
i.remove()
asyncCall.callsPerHost().incrementAndGet()
//3
executableCalls.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
//4
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
分析:
在這之前,注意enqueue的時候先把這個Call加入等待隊列了
- 1:首先把等待隊列遍歷,往下遍歷堡牡,并取出當前的Call妖胀。
- 2:首先判斷當前運行中的隊列大小是不是等于或者超過設定的maxRequests的值爬坑,如果是就break出;如果不會則往下走,判斷當前訪問的主機請求數(shù)等于或者超過設定的當前主機請求數(shù),則跳過繼續(xù)往下下一個Call判斷刚夺;如果不會則往下走箩做,把這個Call從等待隊列中移除(隊列只是用來存儲Call安吁,表示當前的Call的狀態(tài)),并把當前Call訪問的主機數(shù)加一滥玷。
- 3:把可執(zhí)行的Call加入executableCalls拉盾,并加入運行中的隊列倒得。
- 4:假如executableCalls為空這里不走讹躯;不為空則遍歷所有可執(zhí)行的Call骗灶,并把線程池executorService丟給executeOn()萝究。
而這個executeOn是AsyncCall里面的绕娘,那就來看看它是什么東西吧,點進去發(fā)現(xiàn)是RealCall的內(nèi)部類,繼承了Runnable,所以是任務
//AsynCall.class
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
executorService.execute(this)
success = true
} catch (e: RejectedExecutionException) {
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
transmitter.noMoreExchanges(ioException)
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
client.dispatcher.finished(this) // This call is no longer running!
}
}
}
原來把這個線程池丟給它是為了執(zhí)行它的任務,這個任務是它本身,也就是它的run方法
//AsyncCall.class
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
transmitter.timeoutEnter()
try {
//1
val response = getResponseWithInterceptorChain()
signalledCallback = true
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log("Callback failure for ${toLoggableString()}", INFO, e)
} else {
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 {
//2
client.dispatcher.finished(this)
}
}
}
分析:
- 1:getResponseWithInterceptorChain后面會講到获黔,是OkHttp重要的一部分,作用是攔截器處理請求后渗蟹,得到成功的響應結果诗宣,并把結果回調(diào)出去我們使用的地方,也就是我們代碼中的onResponse忘古;假如異常,則把失敗信息回調(diào)出去,對應代碼中的onFailure。
- 2:不管成功失敗,都會走finished。
看看Dispatcher.finished是怎么處理的
//Dispatcher.kt
internal fun finished(call: AsyncCall) {
call.callsPerHost().decrementAndGet()
finished(runningAsyncCalls, call)
}
先把當前訪問主機記錄的個數(shù)減一再走finished(runningAsyncCalls, call)
//Dispatcher.kt
private fun <T> finished(calls: Deque<T>, call: T) {
val idleCallback: Runnable?
synchronized(this) {
//1
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
//2
val isRunning = promoteAndExecute()
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
- 1:在運行隊列中把該Call移除
- 2:又走了一遍promoteAndExecute()驹碍,這里主要是對等待隊列中的Call處理,因為一個運行中的Call處理完柏蘑,就有空出來的位置給等待隊列中的Call執(zhí)行幸冻,所以在異步請求中,每當處理完一個運行中的Call都會去等待隊列里面去取Call洽损,取到則處理,取不到則不處理革半。
所以分發(fā)器異步請求處理流程大概是這個樣子的:
小結一下:
- 如何決定將請求放進ready還是running碑定?
如果當前正在請求數(shù)為64,則將請求放進ready等待隊列又官,如果小于64延刘,但是已經(jīng)存在同一域名主機的請求5個,也會放進ready六敬;否則放進running隊列立即執(zhí)行碘赖。 - 從ready移到running的條件是什么?
每個請求執(zhí)行完成會從running移除外构,同時進行第一步相同邏輯判斷普泡,決定是否移動。 - 分發(fā)器線程池的作用是什么审编?
無等待撼班,高并發(fā)。
異步請求流程講完了垒酬,看看同步請求
//RealCall.kt
override fun execute(): Response {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
transmitter.timeoutEnter()
transmitter.callStart()
try {
//1
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
//2
client.dispatcher.finished(this)
}
}
//Dispatcher.kt
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
- 1:同步請求就比較簡單了砰嘁,先把Call加入運行中的隊列件炉,因為同步隊列所以是在當前線程執(zhí)行而不需要線程池,然后getResponseWithInterceptorChain得到響應結果返回出來矮湘。
- 2:最后會走Dispatcher.finished斟冕,這個方法個異步的finished參數(shù)不一樣,一個是AsyncCall板祝,一個是RealCall宫静。
//Dispatcher.kt
internal fun finished(call: RealCall) {
finished(runningSyncCalls, call)
}
最后調(diào)用的finished和異步請求的一樣,都是從運行隊列移除當前Call券时,然后去等待隊列取消息,得到則處理伏伯,取不到則不處理橘洞。
總結一下分發(fā)器Dispatcher的作用:內(nèi)部維護隊列與線程池,完成請求調(diào)配说搅。所有邏輯大部分都集中在攔截器中炸枣,但進入攔截器之前還需要依靠分發(fā)器來調(diào)配請求任務。
攔截器
下面講講OkHttp重點的攔截器
剛才說到getResponseWithInterceptorChain最終會得到響應結果弄唧,也就是說攔截器這塊是真正進行網(wǎng)絡請求的地方适肠。攔截器采用的是責任鏈模式,什么是責任鏈模式呢候引?舉個例子假如你要點外賣:A是你本人侯养,B是騎手,C是商家
A只負責點外賣和收外賣澄干,不用考慮商家是怎么做的
B是收到訂單后去商家拿外賣逛揩,然后送到你手里
C是把外賣打包好,然后拿給騎手
其實這就是一個U型操作麸俘,一條鏈回去一條鏈回來辩稽,并且除了最后一個外,都有一個前置从媚、中置和后置操作逞泄,最后一個結合起來只是把外賣拿給騎手的操作,也就是中置操作拜效。前置操作是準備喷众,中置操作是往下傳,后置操作是得到結果后做處理拂檩,再往回傳這么一個操作流程侮腹。如果還是不太清楚的話去搜下責任鏈模式的例子。
而在OkHttp攔截器中稻励,就是采用這么一個模式父阻,把所有的攔截器都串起來執(zhí)行愈涩。
//RealCall.kt
@Throws(IOException::class)
fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
//1
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)
//2
val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)
var calledNoMoreExchanges = false
try {
//3
val response = chain.proceed(originalRequest)
if (transmitter.isCanceled) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw transmitter.noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null)
}
}
}
分析:
- 1:把所有需要用的攔截器串起來存在mutableList里面,為什么是可變的List加矛?因為client.interceptors和client.networkInterceptors我們可以自定義自己的攔截器添加進去履婉,所以這兩個的區(qū)別就在于client.interceptors攔截器在整條鏈的鏈頭,而client.networkInterceptors是在鏈尾的前一個(CallServerInterceptor才是鏈尾)
- 2:得到一條鏈子之后斟览,需要對它進行包裝毁腿,主要目的是出入一些參數(shù)后續(xù)使用,例如剛開始的請求數(shù)據(jù)originalRequest苛茂,超時時間等等...
- 3:前面還是準備工作已烤,調(diào)用chain.proceed(originalRequest)才真正執(zhí)行,這條鏈才開始動起來妓羊。為什么能動起來胯究?往下看
//RealInterceptorChain.kt
@Throws(IOException::class)
fun proceed(request: Request, transmitter: Transmitter, exchange: Exchange?): Response {
if (index >= interceptors.size) throw AssertionError()
calls++
// If we already have a stream, confirm that the incoming request will use it.
check(this.exchange == null || this.exchange.connection()!!.supportsUrl(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
check(this.exchange == null || calls <= 1) {
"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
}
// Call the next interceptor in the chain.
//1
val next = RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout)
val interceptor = interceptors[index]
//2
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
// Confirm that the next interceptor made its required call to chain.proceed().
check(exchange == null || index + 1 >= interceptors.size || next.calls == 1) {
"network interceptor $interceptor must call proceed() exactly once"
}
check(response.body != null) { "interceptor $interceptor returned a response with no body" }
return response
}
分析:
- 1:首先先建立一條鏈子next ,其實值都沒變躁绸,只有把索引值加一裕循,這樣鏈子就開始往下;并取到第一個攔截器interceptor (index剛傳進來的時候為0)
- 2:interceptor.intercept(next)净刮,執(zhí)行剛才的第一個攔截器的intercept方法并傳了下一條鏈子進去剥哑。
鏈子組裝好了,并且也轉起來了淹父,那就來看看那些攔截器是干什么的吧(只講標注出來的重點株婴,其他關系不大的代碼可以不看)
重定向攔截器RetryAndFollowUpInterceptor
在不添加攔截器的情況下,第一個攔截器是RetryAndFollowUpInterceptor攔截器弹灭,根據(jù)命名可以猜到是關于重試的攔截器督暂。其實它是重試和重定向攔截器:
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val realChain = chain as RealInterceptorChain
val transmitter = realChain.transmitter()
var followUpCount = 0
var priorResponse: Response? = null
//1
while (true) {
transmitter.prepareToConnect(request)
if (transmitter.isCanceled) {
throw IOException("Canceled")
}
var response: Response
var success = false
try {
//2
response = realChain.proceed(request, transmitter, null)
success = true
} catch (e: RouteException) {
//3
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.lastConnectException, transmitter, false, request)) {
throw e.firstConnectException
}
continue
} catch (e: IOException) {
//4
// An attempt to communicate with a server failed. The request may have been sent.
val requestSendStarted = e !is ConnectionShutdownException
if (!recover(e, transmitter, requestSendStarted, request)) throw e
continue
} finally {
// The network call threw an exception. Release any resources.
if (!success) {
transmitter.exchangeDoneDueToException()
}
}
//5
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
val exchange = response.exchange
val route = exchange?.connection()?.route()
val followUp = followUpRequest(response, route)
//6
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
transmitter.timeoutEarlyExit()
}
return response
}
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
return response
}
response.body?.closeQuietly()
if (transmitter.hasExchange()) {
exchange?.detachWithViolence()
}
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
request = followUp
priorResponse = response
}
}
剛才提到責任鏈是有前置操作、中置操作和后置操作穷吮,那這個攔截器的這些操作是什么逻翁?邊分析邊總結
分析:
- 1:while(true),不斷的去取response捡鱼,直到取到了才返回出來八回,不然一直在里面去取去重試,這就是為什么能重試了驾诈。
- 2:realChain.proceed缠诅,把請求request傳給下一個攔截器,所以這個是中置操作
- 3和4:都是對請求失敗的處理乍迄,從這里開始就是后置操作了管引,這里已經(jīng)得到結果了,如果請求失敗就重試闯两,然后continue重新開始請求褥伴。
- 5:當需要重定向的時候谅将,會得到重定向信息,然后又開始新一輪的請求重慢,只不過這次用的是重定向會回來后的請求饥臂。
- 6:當我不用重試涯塔,也不用重定向了魂莫,那就是拿到最終的response了,返回給外部處理蘸鲸。
那前置工作是什么核芽?
看到剛開始的transmitter.prepareToConnect(request)囚戚,這是對請求前的準備和檢查,里面涉及到了ExchangeFinder狞洋,其實就是創(chuàng)建一個ExchangeFinder對象弯淘,賦值給exchangeFinder,后面在連接攔截器的時候會講到吉懊。
//Transmitter.kt
fun prepareToConnect(request: Request) {
if (this.request != null) {
if (this.request!!.url.canReuseConnectionFor(request.url) && exchangeFinder!!.hasRouteToTry()) {
return // Already ready.
}
check(exchange == null)
if (exchangeFinder != null) {
maybeReleaseConnection(null, true)
exchangeFinder = null
}
}
this.request = request
this.exchangeFinder = ExchangeFinder(
this, connectionPool, createAddress(request.url), call, eventListener)
}
總結一下重定向攔截器的工作:
前置:對請求進行準備和檢查
中置:準備工作完后傳給下一個攔截器
后置:從下個攔截器接收到的信息做處理(重試、重定向或者放回response)
橋接攔截器BridgeInterceptor
接下來看看下一個攔截器假勿,BridgeInterceptor橋接攔截器
//BridgeInterceptor.kt
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
//1
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)
}
//2
val networkResponse = chain.proceed(requestBuilder.build())
//3
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()
}
這個攔截器主要是對我們的請求進一步的包裝借嗽,因為一開始我們的請求只有一條地址路徑,而這個攔截器能解析我們請求转培,并加上一些重要的請求頭信息恶导,這樣我們就不用了手動加上或者本來不知道的請求頭信息(Content-Length等等)。
分析:
- 1:首先在原始請求的基礎上浸须,加上了一些請求頭信息惨寿,組裝成完整的請求信息,還添加上了gzip解析(壓縮能增加傳輸速度)删窒,這是前置工作裂垦。
- 2:把組裝好的請求頭信息傳到下一個攔截器去處理,這是中置工作肌索。
- 3:從下個攔截器得到的response做處理蕉拢,假如支持gzip解析,則解析并返回給上一個攔截器(重試和重定向攔截器)诚亚,否則直接返回上一個攔截器晕换。這是后置工作。
緩存攔截器CacheInterceptor
接下來看下一個攔截器站宗,CacheInterceptor緩存攔截器闸准。緩存攔截器本身主要邏輯其實都在緩存策略中,攔截器本身邏輯非常簡單梢灭,所以先來講下緩存攔截器中的緩存策略CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()夷家,它實際返回一個帶有networkRequest和cacheResponse的CacheStrategy緩存策略:
//CacheStrategy.kt
init {
//1
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis
val headers = cacheResponse.headers
//2
for (i in 0 until headers.size) {
val fieldName = headers.name(i)
val value = headers.value(i)
when {
fieldName.equals("Date", ignoreCase = true) -> {
servedDate = value.toHttpDateOrNull()
servedDateString = value
}
fieldName.equals("Expires", ignoreCase = true) -> {
expires = value.toHttpDateOrNull()
}
fieldName.equals("Last-Modified", ignoreCase = true) -> {
lastModified = value.toHttpDateOrNull()
lastModifiedString = value
}
fieldName.equals("ETag", ignoreCase = true) -> {
etag = value
}
fieldName.equals("Age", ignoreCase = true) -> {
ageSeconds = value.toNonNegativeInt(-1)
}
}
}
}
}
分析:
- 1:若存在候選緩存蒸其,獲取該response請求時發(fā)起和接收的時間(在CallServerInterceptor中記錄的這些時間)
- 2:遍歷header,保存Date瘾英、Expires枣接、Last-Modified、ETag缺谴、Age等緩存機制相關字段的值
//CacheStrategy.kt
private fun computeCandidate(): CacheStrategy {
//1
// No cached response.
if (cacheResponse == null) {
return CacheStrategy(request, null)
}
//2
// Drop the cached response if it's missing a required handshake.
if (request.isHttps && cacheResponse.handshake == null) {
return CacheStrategy(request, null)
}
//3
// If this response shouldn't have been stored, it should never be used as a response source.
// This check should be redundant as long as the persistence store is well-behaved and the
// rules are constant.
if (!isCacheable(cacheResponse, request)) {
return CacheStrategy(request, null)
}
//4
val requestCaching = request.cacheControl
if (requestCaching.noCache || hasConditions(request)) {
return CacheStrategy(request, null)
}
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())
}
//5
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())
}
//6
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
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) // No condition! Make a regular request.
}
val conditionalRequestHeaders = request.headers.newBuilder()
conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)
//7
val conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build()
return CacheStrategy(conditionalRequest, cacheResponse)
}
分析:
- 1:cacheResponse 是從緩存中找到的響應但惶,如果為null,那就表示沒有找到對應的緩存湿蛔,創(chuàng)建的 CacheStrategy 實例膀曾,對象只存在 networkRequest ,這代表了需要發(fā)起網(wǎng)絡請求阳啥。
- 2:繼續(xù)往下走意味著 cacheResponse 必定存在添谊,但是它不一定能用。后續(xù)進行有效性的一系列判斷察迟,如果本次請求是HTTPS斩狱,但是緩存中沒有對應的握手信息,那么緩存無效扎瓶。
- 3:isCacheable方法通過response的狀態(tài)碼以及response所踊、request的緩存控制字段來判斷是否可緩存。緩存響應中的響應碼為 200, 203, 204, 300, 301, 404, 405, 410, 414, 501, 308 的情況下,只判斷服務器是不是給了Cache-Control: no-store (資源不能被緩存)概荷,所以如果服務器給到了這個響應頭秕岛,那就和前面兩個判定一致(緩存不可用)。否則繼續(xù)進一步判斷緩存是否可用
- 4:判斷request中的緩存控制字段和header是否設置If-Modified-Since或If-None-Match误证。走到這一步继薛,OkHttp需要先對用戶本次發(fā)起的 Request 進行判定,如果用戶指定了 Cache-Control: no-cache (不使用緩存)的請求頭或者請求頭包含 If-Modified-Since 或 If-None-Match (請求驗證)愈捅,那么就不允許使用緩存遏考。
- 5:判斷緩存是否在有效期內(nèi)。
- 6:緩存超出有效期改鲫,判斷是否設置了Etag和Last-Modified诈皿。如果都沒有,意味著無法與服務器發(fā)起比較像棘,只能重新請求稽亏。如果有,則添加請求頭缕题,Etag對應If-None-Match截歉、Last-Modified對應If-Modified-Since
- 7:至此,緩存的判定結束烟零,攔截器中只需要判斷 CacheStrategy 中 networkRequest 與 cacheResponse 的不同組合就能夠判斷是否允許使用緩存瘪松。
注意:但是需要注意的是咸作,如果用戶在創(chuàng)建請求時,配置了 onlyIfCached 這意味著用戶這次希望這個請求只從緩存獲得宵睦,不需要發(fā)起請求记罚。那如果生成的 CacheStrategy 存在 networkRequest 這意味著肯定會發(fā)起請求,此時出現(xiàn)沖突壳嚎!那會直接給到攔截器一個既沒有 networkRequest 又沒有 cacheResponse 的對象桐智。攔截器直接返回用戶 504 !
講完緩存策略現(xiàn)在來看緩存攔截器就很簡單了:
//CacheStrategy.kt
fun compute(): CacheStrategy {
val candidate = computeCandidate()
// We're forbidden from using the network and the cache is insufficient.
if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
return CacheStrategy(null, null)
}
return candidate
}
總結下緩存策略:
1烟馅、如果從緩存獲取的 Response 是null说庭,那就需要使用網(wǎng)絡請求獲取響應;
2郑趁、如果是Https請求刊驴,但是又丟失了握手信息,那也不能使用緩存寡润,需要進行網(wǎng)絡請求捆憎;
3、如果判斷響應碼不能緩存且響應頭有 no-store 標識梭纹,那就需要進行網(wǎng)絡請求攻礼;
4、如果請求頭有 no-cache 標識或者有 If-Modified-Since/If-None-Match 栗柒,那么需要進行網(wǎng)絡請求;
5知举、如果響應頭沒有 no-cache 標識瞬沦,且緩存時間沒有超過極限時間,那么可以使用緩存雇锡,不需要進行網(wǎng)絡請求逛钻;
6、如果緩存過期了锰提,判斷響應頭是否設置 Etag/Last-Modified/Date 曙痘,沒有那就直接使用網(wǎng)絡請求否則需要考慮服務器返回304;
并且立肘,只要需要進行網(wǎng)絡請求边坤,請求頭中就不能包含 only-if-cached ,否則框架直接返回504谅年!
//CacheInterceptor.kt
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//1
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
//2
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
cache?.trackResponse(strategy)
if (cacheCandidate != null && cacheResponse == null) {
// The cache candidate wasn't applicable. Close it.
cacheCandidate.body?.closeQuietly()
}
//3
// If we're forbidden from using the network and the cache is insufficient, fail.
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()
}
//4
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build()
}
var networkResponse: Response? = null
try {
//5
networkResponse = chain.proceed(networkRequest)
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
cacheCandidate.body?.closeQuietly()
}
}
//6
// If we have a cache response too, then we're doing a conditional get.
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()
networkResponse.body!!.close()
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache!!.trackConditionalCacheHit()
cache.update(cacheResponse, response)
return response
} else {
cacheResponse.body?.closeQuietly()
}
}
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
//7
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)
}
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
return response
}
緩存攔截器說復雜不復雜茧痒,說簡單不簡單,把流程縷清就好了融蹂。
- 強制緩存旺订,即緩存在有效期內(nèi)就直接返回緩存弄企,不進行網(wǎng)絡請求。
- 對比緩存区拳,即緩存超過有效期拘领,進行網(wǎng)絡請求。若數(shù)據(jù)未修改樱调,服務端返回不帶body的304響應约素,表示客戶端緩存仍有效可用;否則返回完整最新數(shù)據(jù)本涕,客戶端取網(wǎng)絡請求的最新數(shù)據(jù)业汰。
分析:
- 1:cache即構建OkHttpClient時傳入的Cache對象的內(nèi)部成員,用request的url作為key菩颖,查找緩存的response作為候選緩存样漆,當沒傳Cache對象時,cacheCandidate 為空晦闰,cacheCandidate = null
- 2:生成緩存策略放祟,獲取緩存策略生成的networkRequest和cacheResponse(合法緩存),networkRequest是否為空決定是否請求網(wǎng)絡呻右,cacheResponse是否為空決定是否使用緩存跪妥,下面流程會根據(jù)生成的networkRequest和cacheResponse來決定執(zhí)行什么緩存策略。
- 3:即無網(wǎng)絡請求又無合法緩存声滥,返回狀態(tài)碼504的response(HTTP_GATEWAY_TIMEOUT = 504),
- 4:強制緩存策略
- 5:調(diào)用下一層攔截器
- 6:當服務端放回networkResponse后眉撵,執(zhí)行對比緩存策略,服務端返回304狀態(tài)碼(HTTP_NOT_MODIFIED = 304)落塑,表示本地緩存仍有效
- 7:判斷能否緩存networkResponse纽疟,實現(xiàn)本次緩存
總結一下:
networkRequest、cacheResponse均為空憾赁,構建返回一個狀態(tài)碼為504的response污朽;
networkRequest為空、cacheResponse不為空龙考,執(zhí)行強制緩存
networkRequest蟆肆、cacheResponse均不為空,執(zhí)行對比緩存
networkRequest不為空晦款、cacheResponse為空炎功,網(wǎng)絡請求獲取到最新response后,視情況緩存response
緩存只能緩存GET請求的響應
緩存攔截器本身主要邏輯其實都在緩存策略中柬赐,攔截器本身邏輯非常簡單亡问,如果確定需要發(fā)起網(wǎng)絡請求,則下一個攔截器為 ConnectInterceptor
連接攔截器ConnectInterceptor
接著下一個ConnectInterceptor連接攔截器
//ConnectInterceptor.kt
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val request = realChain.request()
val transmitter = realChain.transmitter()
// We need the network to satisfy this request. Possibly for validating a conditional GET.
val doExtensiveHealthChecks = request.method != "GET"
val exchange = transmitter.newExchange(chain, doExtensiveHealthChecks)
return realChain.proceed(request, transmitter, exchange)
}
代碼也是很簡單,但里面的邏輯很多州藕,只有前置和中置操作束世,沒有后置操作,因為這里是建立一個連接去發(fā)請求的床玻,所以沒有后置工作毁涉。這個攔截器中最重要的是調(diào)用newExchange然后new一個exchange出來傳給下一個攔截器
//Transmitter.kt
internal fun newExchange(chain: Interceptor.Chain, doExtensiveHealthChecks: Boolean): Exchange {
synchronized(connectionPool) {
check(!noMoreExchanges) { "released" }
check(exchange == null) {
"cannot make a new request because the previous response is still open: " +
"please call response.close()"
}
}
//1
val codec = exchangeFinder!!.find(client, chain, doExtensiveHealthChecks)
//2
val result = Exchange(this, call, eventListener, exchangeFinder!!, codec)
synchronized(connectionPool) {
this.exchange = result
this.exchangeRequestDone = false
this.exchangeResponseDone = false
return result
}
}
重點只看1、2锈死,分析:
- 1:調(diào)用exchangeFinder.find得到一個codec(code & decoder)贫堰,連接的編碼解碼器,而這個exchangeFinder就是在重試和重定向攔截器做前置工作時候準備的待牵,在這里就用上了其屏。
- 2:然后得到編碼解碼器codec后拼成一個Exchange返回出去。
注意:編碼解碼器就是在發(fā)請求報文和接收請求報文需要按照格式去讀缨该,可以是http1格式也可以是http2格式偎行,不同格式就是不用編碼。
看看怎么得到codec的
//ExchangeFinder.kt
fun find(
client: OkHttpClient,
chain: Interceptor.Chain,
doExtensiveHealthChecks: Boolean
): ExchangeCodec {
val connectTimeout = chain.connectTimeoutMillis()
val readTimeout = chain.readTimeoutMillis()
val writeTimeout = chain.writeTimeoutMillis()
val pingIntervalMillis = client.pingIntervalMillis
val connectionRetryEnabled = client.retryOnConnectionFailure
try {
//1
val resultConnection = findHealthyConnection(
connectTimeout = connectTimeout,
readTimeout = readTimeout,
writeTimeout = writeTimeout,
pingIntervalMillis = pingIntervalMillis,
connectionRetryEnabled = connectionRetryEnabled,
doExtensiveHealthChecks = doExtensiveHealthChecks
)
//2
return resultConnection.newCodec(client, chain)
} catch (e: RouteException) {
trackFailure()
throw e
} catch (e: IOException) {
trackFailure()
throw RouteException(e)
}
}
得到codec需要兩個步驟:
- 1:獲取一個健康的連接贰拿。
- 2:然后用健康的鏈接根據(jù)client和chain創(chuàng)建一個對應的編碼解碼器蛤袒。
怎么得到一個健康的連接呢?
//ExchangeFinder.kt
@Throws(IOException::class)
private fun findHealthyConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
doExtensiveHealthChecks: Boolean
): RealConnection {
while (true) {
//1
val candidate = findConnection(
connectTimeout = connectTimeout,
readTimeout = readTimeout,
writeTimeout = writeTimeout,
pingIntervalMillis = pingIntervalMillis,
connectionRetryEnabled = connectionRetryEnabled
)
// If this is a brand new connection, we can skip the extensive health checks.
synchronized(connectionPool) {
if (candidate.successCount == 0) {
return candidate
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
//2
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
candidate.noNewExchanges()
continue
}
return candidate
}
}
分析:
- 1:創(chuàng)建一個連接出來膨更。
- 2:然后判斷健不健康妙真,不健康的話就繼續(xù)continue(注意while),直到取到一個健康的連接返回出去荚守。
注意:我們的目的是要得到一個編碼解碼器codec才引申出健康的連接珍德。
那么怎么創(chuàng)建一個連接呢?
//ExchangeFinder.kt
@Throws(IOException::class)
private fun findConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean
): RealConnection {
var foundPooledConnection = false
var result: RealConnection? = null
var selectedRoute: Route? = null
var releasedConnection: RealConnection?
val toClose: Socket?
synchronized(connectionPool) {
if (transmitter.isCanceled) throw IOException("Canceled")
hasStreamFailure = false // This is a fresh attempt.
//1
releasedConnection = transmitter.connection
toClose = if (transmitter.connection != null && transmitter.connection!!.noNewExchanges) {
transmitter.releaseConnectionNoEvents()
} else {
null
}
//2
if (transmitter.connection != null) {
// We had an already-allocated connection and it's good.
result = transmitter.connection
releasedConnection = null
}
//3
if (result == null) {
// Attempt to get a connection from the pool.
//4
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true
result = transmitter.connection
} else if (nextRouteToTry != null) {
selectedRoute = nextRouteToTry
nextRouteToTry = null
} else if (retryCurrentRoute()) {
selectedRoute = transmitter.connection!!.route()
}
}
}
toClose?.closeQuietly()
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection!!)
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result!!)
}
//5
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
return result!!
}
// If we need a route selection, make one. This is a blocking operation.
var newRouteSelection = false
if (selectedRoute == null && (routeSelection == null || !routeSelection!!.hasNext())) {
newRouteSelection = true
routeSelection = routeSelector.next()
}
var routes: List<Route>? = null
synchronized(connectionPool) {
if (transmitter.isCanceled) throw IOException("Canceled")
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
routes = routeSelection!!.routes
//6
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
foundPooledConnection = true
result = transmitter.connection
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection!!.next()
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
//7
result = RealConnection(connectionPool, selectedRoute!!)
connectingConnection = result
}
}
// If we found a pooled connection on the 2nd time around, we're done.
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result!!)
return result!!
}
//8
// Do TCP + TLS handshakes. This is a blocking operation.
result!!.connect(
connectTimeout,
readTimeout,
writeTimeout,
pingIntervalMillis,
connectionRetryEnabled,
call,
eventListener
)
connectionPool.routeDatabase.connected(result!!.route())
var socket: Socket? = null
synchronized(connectionPool) {
connectingConnection = null
// Last attempt at connection coalescing, which only occurs if we attempted multiple
// concurrent connections to the same host.
//9
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
// We lost the race! Close the connection we created and return the pooled connection.
result!!.noNewExchanges = true
socket = result!!.socket()
result = transmitter.connection
// It's possible for us to obtain a coalesced connection that is immediately unhealthy. In
// that case we will retry the route we just successfully connected with.
nextRouteToTry = selectedRoute
} else {
//10
connectionPool.put(result!!)
transmitter.acquireConnectionNoEvents(result!!)
}
}
socket?.closeQuietly()
eventListener.connectionAcquired(call, result!!)
return result!!
}
代碼比較長矗漾,只分析重點
分析:
因為初次進來的時候菱阵,所有都是默認值,都為空缩功,所以1和2中的connection為空,直接看到3都办,之后再看1和2嫡锌。
- 3:剛進來的時候result初始值為空,來到了4.
- 4:調(diào)用連接池connectionPool的transmitterAcquirePooledConnection琳钉,主要是去連接池里面拿連接势木。
//RealConnectionPool.kt
fun transmitterAcquirePooledConnection(
address: Address,
transmitter: Transmitter,
routes: List<Route>?,
requireMultiplexed: Boolean
): Boolean {
this.assertThreadHoldsLock()
for (connection in connections) {
if (requireMultiplexed && !connection.isMultiplexed) continue
if (!connection.isEligible(address, routes)) continue
transmitter.acquireConnectionNoEvents(connection)
return true
}
return false
}
循環(huán)遍歷連接池里面的連接,因為剛才4傳進來的requireMultiplexed = false歌懒,所以往下走啦桌,只要connection.isEligible(address, routes)為true,代表這個可用,那我就直接拿出這條連接來使用甫男,acquireConnectionNoEvents(connection)賦值到Transmitter的connection且改,下次就用到的時候就不為空了。
注意:我們的目的是要得到一個編碼解碼器codec才引申出健康的連接再到怎么取連接板驳。
那么問題又來了又跛,什么才算可用的連接呢?
//RealConnection.kt
internal fun isEligible(address: Address, routes: List<Route>?): Boolean {
//1
// If this connection is not accepting new exchanges, we're done.
if (transmitters.size >= allocationLimit || noNewExchanges) return false
//2
// If the non-host fields of the address don't overlap, we're done.
if (!this.route.address.equalsNonHost(address)) return false
//3
// If the host exactly matches, we're done: this connection can carry the address.
if (address.url.host == this.route().address.url.host) {
return true // This connection is a perfect match.
}
// At this point we don't have a hostname match. But we still be able to carry the request if
// our connection coalescing requirements are met. See also:
// https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
// https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/
// 1. This connection must be HTTP/2.
if (http2Connection == null) return false
// 2. The routes must share an IP address.
if (routes == null || !routeMatchesAny(routes)) return false
// 3. This connection's server certificate's must cover the new host.
if (address.hostnameVerifier !== OkHostnameVerifier) return false
if (!supportsUrl(address.url)) return false
// 4. Certificate pinning must match the host.
try {
address.certificatePinner!!.check(address.url.host, handshake()!!.peerCertificates)
} catch (_: SSLPeerUnverifiedException) {
return false
}
return true // The caller's address can be carried by this connection.
}
這里可分為兩部分若治,一部分是http1的慨蓝,一部分是http2的。
分析:
- 1:Http1的情況系allocationLimit為1端幼,Http2的情況下allocationLimit為4礼烈,transmitters.size是連接的請求數(shù)量,在http1時婆跑,每個http連接只能接受一個請求此熬,noNewExchanges代表目前連接是否還接受新的請求。所以洽蛀,假如我還接受別的請求摹迷,并且算上這個請求沒有超出allocationLimit,繼續(xù)往下走郊供,否則繼續(xù)返回false峡碉。
- 2:現(xiàn)在的這個請求是否與連接的請求是否連在同個地方,也就是一下這些值要相等驮审,包括端口以及加密信息鲫寄、代理配置等等...
//Address.kt
internal fun equalsNonHost(that: Address): Boolean {
return this.dns == that.dns &&
this.proxyAuthenticator == that.proxyAuthenticator &&
this.protocols == that.protocols &&
this.connectionSpecs == that.connectionSpecs &&
this.proxySelector == that.proxySelector &&
this.proxy == that.proxy &&
this.sslSocketFactory == that.sslSocketFactory &&
this.hostnameVerifier == that.hostnameVerifier &&
this.certificatePinner == that.certificatePinner &&
this.url.port == that.url.port
}
- 3:目標主機名與連接的主機名相同就返回
可能你會覺得,這樣做就可以判斷可不可用了疯淫,那下面代碼是干嘛的地来?主要是針對Http2的(包括是Https的情況下),其實還有一種叫連接合并(connection coalescing)的東西熙掺,這種情況是不同域名指向了同一主機IP未斑,這種情況是存在的,所以我們可以不要求域名要一致币绩。但是怎么保證我們解析到相同IP能正確訪問到我們的想訪問的主機呢蜡秽?可以看他們的證書簽名。
假如域名不一樣缆镣,繼續(xù)往下看芽突,這個連接必須是Http2的情況下才可能存在可用的鏈接,否則直接返回false董瞻。接著routes(Route后面會講到)不為空并且IP和代理模式要一樣寞蚌,才會往下走,但是第一次傳進來的時候routes是空的,所以第一次如果不在前面取到可用鏈接的話挟秤,后面就直接返回壹哺,然后回到findConnection里面繼續(xù)往下走;所以routes是判斷是否能連接合并煞聪,假如都有了斗躏,那往下走,證書簽名昔脯、端口也要一樣才能算一個可用的連接啄糙。
所以isEligible中,可用的連接是:我的請求個數(shù)沒超限云稚,并且用同樣的方式連接同一個主機隧饼。
注意:我們的目的是要得到一個編碼解碼器codec才引申出健康的連接再到怎么取連接。
回到剛才長長代碼的findConnection里面繼續(xù)静陈,現(xiàn)在我們就知道了怎么去連接池里面取一個可用的鏈接了燕雁,接著往下:
- 5:假如4取到后可用的連接后,之后返回鲸拥,下面就不走了拐格,出去判斷健不健康了。
- 6:如果在第一次取的時候拿不到刑赶,那么再去拿一次捏浊,只不過這次對比第一次多了個routes,那么就講一下Address和Route是什么撞叨?
Address:里面兩個信息金踪,一個是主機名,一個是端口牵敷,有了這個就可以直接解析得到IP然后結合代理類型找到真的代理的主機胡岔。而這個Address是在第一個攔截器準備工作創(chuàng)建exchangeFinder的時候傳進去的。Address除了主機名uriHost和端口uriPort枷餐,其他參數(shù)都是從OkHttpClient里面取到的靶瘸。
Route:包含了Address(域名和端口)、Proxy代理和IP地址毛肋。合并連接中需要Route中結合Proxy和IP地址端口決定的奕锌,所以傳了Route才能找到連接路線才能進行連接合并。
同樣拿到之后也是會返回result = transmitter.connection - 7:假如第二次還是沒取到村生,foundPooledConnection = false,那就自己創(chuàng)建一個連接饼丘。
- 8:建立一個連接趁桃。(7只是創(chuàng)建)
- 9:為什么還要再去連接池里面找呢?當連接池里面沒有對應的可用連接時,第一次和第二次肯定找不到卫病,那么就要自己創(chuàng)建連接了油啤,此時剛好有兩個請求都是指向同一個連接,那么創(chuàng)建兩個相同的連接就浪費資源了蟀苛。所以先創(chuàng)建好的去連接池里面找找不到益咬,那么就到了10(直接把連接塞進連接池);之后因為synchronized帜平,后面進來的去連接池里面找幽告,發(fā)現(xiàn)找到了,就把剛才自己創(chuàng)建好的連接扔掉裆甩。這里創(chuàng)建的連接都是都是多路復用的冗锁,因為剛才請求都是同個路徑,所以需要多路復用嗤栓。
- 10:把新建的連接放進連接池冻河。
剛才說初次進來的時候connection肯定是空的,但是現(xiàn)在創(chuàng)建完就不為空茉帅,那么這里是什么時候調(diào)用的叨叙?當請求失敗,重定向的時候堪澎。 - 1:當我重新回來的時候擂错,發(fā)現(xiàn)有新的連接,并且不能重新復用的全封,那就把這個連接設為關閉马昙,到后面再把連接斷開。
- 2:經(jīng)過1之后刹悴,說明這連接時可以用的行楞,那就直接拿這連接來使用。
總結一下:去找可用連接有五個操作
1土匀、看看有沒有可用連接子房,有可用連接且符合就直接用。
2就轧、從池子里面拿一個不帶多路復用的連接证杭。
3、從池子里面拿一個可以多路復用的連接妒御。
4解愤、創(chuàng)建連接。
5乎莉、創(chuàng)建過連接送讲,去池子里面拿奸笤,只拿多路復用的連接,拿到的話就把剛才創(chuàng)建的扔掉哼鬓。
注意:我們的目的是要得到一個編碼解碼器codec才引申出健康的連接再到怎么取連接监右。
現(xiàn)在來看下什么是這個取到的連接怎么才是健康的
//RealConnection.kt
/** Returns true if this connection is ready to host new streams. */
fun isHealthy(doExtensiveChecks: Boolean): Boolean {
val socket = this.socket!!
val source = this.source!!
if (socket.isClosed || socket.isInputShutdown || socket.isOutputShutdown) {
return false
}
val http2Connection = this.http2Connection
if (http2Connection != null) {
return http2Connection.isHealthy(System.nanoTime())
}
if (doExtensiveChecks) {
try {
val readTimeout = socket.soTimeout
try {
socket.soTimeout = 1
return !source.exhausted()
} finally {
socket.soTimeout = readTimeout
}
} catch (_: SocketTimeoutException) {
// Read timed out; socket is good.
} catch (_: IOException) {
return false // Couldn't read; socket is closed.
}
}
return true
}
這里面主要看socket關閉沒有,再看看http2健不健康也就是http2的是否超出心跳時間异希。
所以到這里就已經(jīng)有了一個健康可用的連接健盒,用于創(chuàng)建一個對應的編碼解碼器codec
回到連接攔截器里面的調(diào)用Transmitter.newExchange,現(xiàn)在codec有了称簿,這個codec不僅包含了編碼解碼器扣癣,還有一個可用的健康的連接。再把這個codec裝進Exchange予跌,然后返回給連接攔截器搏色,最后連接攔截器把exchange傳給下一個攔截器。
請求攔截器CallServerInterceptor
接著最后一個攔截器券册,CallServerInterceptor請求攔截器
//CallServerInterceptor.kt
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.exchange()
val request = realChain.request()
val requestBody = request.body
val sentRequestMillis = System.currentTimeMillis()
exchange.writeRequestHeaders(request)
var invokeStartEvent = true
var responseBuilder: Response.Builder? = null
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
exchange.flushRequest()
responseBuilder = exchange.readResponseHeaders(expectContinue = true)
exchange.responseHeadersStart()
invokeStartEvent = false
}
if (responseBuilder == null) {
if (requestBody.isDuplex()) {
// Prepare a duplex body so that the application can send a request body later.
exchange.flushRequest()
val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
requestBody.writeTo(bufferedRequestBody)
} else {
// Write the request body if the "Expect: 100-continue" expectation was met.
val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
requestBody.writeTo(bufferedRequestBody)
bufferedRequestBody.close()
}
} else {
exchange.noRequestBody()
if (!exchange.connection()!!.isMultiplexed) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
exchange.noNewExchangesOnConnection()
}
}
} else {
exchange.noRequestBody()
}
if (requestBody == null || !requestBody.isDuplex()) {
exchange.finishRequest()
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
if (invokeStartEvent) {
exchange.responseHeadersStart()
invokeStartEvent = false
}
}
var response = responseBuilder
.request(request)
.handshake(exchange.connection()!!.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
var code = response.code
if (code == 100) {
// Server sent a 100-continue even though we did not request one. Try again to read the actual
// response status.
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
if (invokeStartEvent) {
exchange.responseHeadersStart()
}
response = responseBuilder
.request(request)
.handshake(exchange.connection()!!.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
code = response.code
}
exchange.responseHeadersEnd(response)
response = if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response.newBuilder()
.body(EMPTY_RESPONSE)
.build()
} else {
response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
}
if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
"close".equals(response.header("Connection"), ignoreCase = true)) {
exchange.noNewExchangesOnConnection()
}
if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
throw ProtocolException(
"HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
}
return response
}
這個攔截器就比較簡單频轿,主要是去發(fā)請求和讀響應,然后把響應往回傳這么一個操作烁焙,因為責任鏈中最后一個點沒有前置和后置航邢,主要是把前面攔截器做的事情進行最后的請求和返回響應。這樣整個結果往回傳骄蝇,最終回到了RealCall里面的response = getResponseWithInterceptorChain()
總結
整個OkHttp功能的實現(xiàn)就在這五個默認的攔截器中膳殷,所以先理解攔截器模式的工作機制是先決條件。這五個攔截器分別為: 重試攔截器九火、橋接攔截器赚窃、緩存攔截器、連接攔截器岔激、請求服務攔截器勒极。每一個攔截器負責的工作不一樣,就好像工廠流水線虑鼎,最終經(jīng)過這五道工序辱匿,就完成了最終的產(chǎn)品。但是與流水線不同的是,OkHttp中的攔截器每次發(fā)起請求都會在交給下一個攔截器之前干一些事情,在獲得了結果之后又干一些事情计维。整個過程在請求向是順序的,而響應向則是逆序晚顷。
當用戶發(fā)起一個請求后,會由任務分發(fā)起 Dispatcher 將請求包裝并交給重試攔截器處理。
1、重試攔截器在交出(交給下一個攔截器)之前邑贴,負責判斷用戶是否取消了請求限府;在獲得了結果之后,會根據(jù)響應碼判斷是否需要重定向痢缎,如果滿足條件那么就會重啟執(zhí)行所有攔截器。
2世澜、橋接攔截器在交出之前独旷,負責將HTTP協(xié)議必備的請求頭加入其中(如:Host)并添加一些默認的行為(如:GZIP壓縮);在獲得了結果后寥裂,調(diào)用保存cookie接口并解析GZIP數(shù)據(jù)嵌洼。
3、緩存攔截器顧名思義封恰,交出之前讀取并判斷是否使用緩存麻养;獲得結果后判斷是否緩存。
4诺舔、連接攔截器在交出之前鳖昌,負責找到或者新建一個連接,并獲得對應的socket流低飒;在獲得結果后不進行額外的處理许昨。
5、請求服務器攔截器進行真正的與服務器的通信褥赊,向服務器發(fā)送數(shù)據(jù)糕档,解析讀取的響應數(shù)據(jù)。
在經(jīng)過了這一系列的流程后拌喉,就完成了一次HTTP請求速那!
OkHttp的優(yōu)點:
- 支持Http1、Http2
- 連接池復用底層TCP尿背,減少請求延時
- 無縫的支持GZIP減少數(shù)據(jù)流量端仰,提高傳輸效率
- 緩存響應數(shù)據(jù)減少重復的網(wǎng)絡請求
- 請求失敗自動重試,自動重定向