OkHttp源碼解析筆記
本篇OkHttp源碼基於3.0 Kotlin版本
1.Retrofit的基本使用
首先看OkHttp的基本使用
fun main(){
val okHttpClient = OkHttpClient().newBuilder().build()
val request = Request.Builder()
.method("", null)
.url("")
.build()
val call = okHttpClient.newCall(request)
//同步
call.execute()
//異步
call.enqueue(object: Callback{
override fun onFailure(call: Call, e: IOException) {}
override fun onResponse(call: Call, response: Response) {}
}
)
}
2.從OkHttp的基本API入手:
從OkHttpClient類的newCall函數出發,看下面代碼註釋
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
override fun newCall(request: Request): Call {
//RealCall是OkHttp的核心類,
//分別從RealCall類的enqueue和execute入手看源碼
return RealCall.newRealCall(this, request, forWebSocket = false)
}
}
接着看RealCall類的同步請求函數,將同步請求交給任務調度類Dispatcher
internal class RealCall private constructor(
val client: OkHttpClient,val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
override fun execute(): Response {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
transmitter.timeoutEnter()
transmitter.callStart()
try {
//將同步請求交給任務調度類 Dispatcher
client.dispatcher.executed(this)
//核心在這個getResponseWithInterceptorChain函數
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
}
class Dispatcher constructor() {
@Synchronized internal fun executed(call: RealCall) {
//添加到同步運行隊列中
runningSyncCalls.add(call)
}
}
可以看到同步請求走到getResponseWithInterceptorChain函數,這裏我們先卡住,先看異步請求
internal class RealCall private constructor(
val client: OkHttpClient,val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
//http異步執行請求的調用從這裏開始
override fun enqueue(responseCallback: Callback) {
//是否已執行
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
transmitter.callStart()
// 將異步任務 交給 Dispatcher(調度器) 去執行
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
}
可以看到異步請求同樣交給任務調度器Dispatcher,接着看Dispatcher類的enqueue函數
class Dispatcher constructor() {
//okHttp調用的enqueue進行異步請求
//實際是調用到這裏的Dispatcher(調度器)類的enqueue函數
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
//添加到異步等待隊列
readyAsyncCalls.add(call)
//這裏的forWebSocket變量已經在OkHttpClient的newCall函數中給賦予 false
if (!call.get().forWebSocket) {
val existingCall = findExistingCallWithHost(call.host())
// 使call共享已存在的同一主機
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute() //重點從這裏開始
}
enqueue函數的核心在調用了promoteAndExecute函數,promoteAndExecute函數內的主要工作:
- 1.遍歷等待隊列,滿足 “運行隊列的個數不超過64個,同一個host不超過5條線程執行請求” 的條件下,將等待線程添加到到運行隊列中,並且執行該線程
- 2.當前還有運行請求中的線程 則返回true, 否則返回false
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator() //readyAsyncCalls: 異步等待線程隊列
//對異步等待隊列進行迭代
while (i.hasNext()) {
val asyncCall = i.next()
//能進行的最大請求數不能超過 maxRequests = 64 個
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
// 同一個host最多允許maxRequestsPerHost = 5條線程通知執行請求
if (asyncCall.callsPerHost().get() >= this.maxRequestsPerHost) continue // Host max capacity.
i.remove()
asyncCall.callsPerHost().incrementAndGet() //同一個host +1
executableCalls.add(asyncCall) //將異步任務加入執行的集合裏
runningAsyncCalls.add(asyncCall) //將異步任務加入運行的隊列裏
}
isRunning = runningCallsCount() > 0
}
//線程池裏執行異步請求
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
}
接着看asyncCall.executeOn(executorService)這句代碼,這句代碼是執行 添加到運行隊列的線程,可以看到此處的AsyncCall是RealCall類的內部類,AyncCall繼承自Runnable
internal class RealCall private constructor(
val client: OkHttpClient, val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
//線程池裏執行線程,執行下面的run函數
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!
}
}
}
//核心在run函數的getResponseWithInterceptorChain方法
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
transmitter.timeoutEnter()
try {
//這裏的getResponseWithInterceptorChain函數是OkHttp的核心,
//同步、異步請求都會調用getResponseWithInterceptorChain這個函數
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 {
client.dispatcher.finished(this)
}
}
}
}
}
在這裏,異步請求跟同步請求一樣執行到getResponseWithInterceptorChain這個函數,接着我們先看下上面代碼處的兩處finally,都調用到finished
class Dispatcher constructor() {
internal fun finished(call: AsyncCall) {
//將運行的地址host集合 減 1
call.callsPerHost().decrementAndGet()
finished(runningAsyncCalls, call)
}
private fun <T> finished(calls: Deque<T>, call: T) {
val idleCallback: Runnable?
synchronized(this) {
//將當前無響應的線程 或 執行完成的線程 進行移除
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
//這裏之所以執行promoteAndExecute函數,是要檢查等待隊列是否存在等待線程,如果存在,遍歷等待隊列的線程並
//檢驗執行隊列的線程數是否達到最大數(64),如果沒有達到最大數,則將等待線程加入到請求隊列中,這樣也就確保了等待隊列能夠
//及時添加到執行隊列,確保了等待隊列的線程能被執行
val isRunning = promoteAndExecute()
if (!isRunning && idleCallback != null) {//沒有運行的線程
idleCallback.run()//啓動之前閒置的線程
}
}
}
可以看到Dispatcher類的finished函數主要做了兩件事:
- 移除 無響應線程或執行完成的線程
- 調用上面提到的promoteAndExecute函數,對等待隊列和運行隊列進行調度,使得等待線程可以及時被執行
接着回到getResponseWithInterceptorChain這個函數,可以看到主要配置了多個攔截器,而這些攔截器負責對請求數據和響應數據進行處理,內部採用的是責任鏈模式, 然後創建RealInterceptorChain這個責任鏈的調度類,調用其proceed函數開啓責任鏈
@Throws(IOException::class)
fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
//TODO 責任鏈
val interceptors = mutableListOf<Interceptor>()
//TODO 在配置okhttpClient 時設置的intercept 由用戶自己設置
interceptors += client.interceptors
//TODO 負責處理失敗後的重試與重定向
interceptors += RetryAndFollowUpInterceptor(client)
//TODO 深加工用戶傳遞來的請求,設置默認請求頭;用戶沒有設置時默認採用 gzip 壓縮解壓數據
interceptors += BridgeInterceptor(client.cookieJar)
//TODO 處理 緩存配置 根據條件(存在響應緩存並被設置爲不變的或者響應在有效期內)返回緩存響應
//TODO 設置請求頭(If-None-Match、If-Modified-Since等) 服務器可能返回304(未修改)
//TODO 可配置用戶自己設置的緩存攔截器
interceptors += CacheInterceptor(client.cache)
//TODO 連接服務器,負責和服務器建立連接
interceptors += ConnectInterceptor
if (!forWebSocket) {
//TODO 配置OkhttpClient 時設置的networkInterceptors
//TODO 返回觀察單個網絡請求和響應的不可變攔截器列表。
interceptors += client.networkInterceptors
}
//TODO 執行流操作(寫出請求體、獲得響應數據) 負責向服務器發送請求數據、從服務器讀取響應數據
//TODO 進行http請求報文的封裝與請求報文的解析
interceptors += CallServerInterceptor(forWebSocket)
//TODO 創建責任鏈
val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)
var calledNoMoreExchanges = false
try {
//TODO 執行責任鏈
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)
}
}
}
下面看下CacheInterceptor緩存攔截器部分的實現, 可以看到:
- 緩存攔截器會根據請求的信息和緩存的響應的信息來判斷是否存在緩存可用;
- 如果有可以使用的緩存,那麼就返回該緩存給用戶,否則就繼續使用責任鏈模式來從服務器中獲取響應;
- 當獲取到響應的時候,又會把響應緩存到磁盤上面,最後返回reseponse
class CacheInterceptor(internal val cache: Cache?): Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//根據request得到cache中緩存的response,
// 如果用戶沒有配置緩存攔截器: cacheCandidate == null,則說明cache(用戶自配的緩存攔截器)也是爲null
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
// request判斷緩存策略,是否使用了網絡、緩存或兩者都使用
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
//如果networkRequest == null 則說明不使用網絡請求
val networkRequest = strategy.networkRequest
//獲取緩存中(CacheStrategy)的Response
val cacheResponse = strategy.cacheResponse
cache?.trackResponse(strategy)
//緩存無效 關閉資源
if (cacheCandidate != null && cacheResponse == null) {
// The cache candidate wasn't applicable. Close it.
cacheCandidate.body?.closeQuietly()
}
// If we're forbidden from using the network and the cache is insufficient, fail.
//networkRequest == null 不使用網路請求 且沒有緩存 cacheResponse == null 返回失敗
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()
}
// 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 {
//調用下一個攔截器,從網絡上獲取數據
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()
}
}
// If we have a cache response too, then we're doing a conditional get.
// 如果本地已經存在cacheResponse,那麼讓它和網絡得到的networkResponse做比較,決定是否來更新緩存的cacheResponse
if (cacheResponse != null) {
//HTTP_NOT_MODIFIED = 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()
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache!!.trackConditionalCacheHit()
cache.update(cacheResponse, response) //更新DiskLruCache緩存
return response
} else {
cacheResponse.body?.closeQuietly()
}
}
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
//cache不爲null,說明用戶自己配置了緩存攔截器
if (cache != null) {
// 緩存未經緩存過的response
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
}
}
接着看ConnectInterceptor連接攔截器的實現,可以看到
- 1.判斷當前的連接是否可以使用:流是否已經被關閉,並且已經被限制創建新的流;
- 2.如果當前的連接無法使用,就從連接池中獲取一個連接;
- 3.連接池中也沒有發現可用的連接,創建一個新的連接,並進行握手,然後將其放到連接池中。
- 補充:連接複用的一個好處就是省去了進行 TCP 和 TLS 握手的一個過程。因爲建立連接本身也是需要消耗一些時間的,連接被複用之後可以提升我們網絡訪問的效率
class ExchangeFinder(
private val transmitter: Transmitter,
private val connectionPool: RealConnectionPool,
private val address: Address,
private val call: Call,
private val eventListener: EventListener
) {
@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.
// 嘗試使用已分配的連接,已經分配的連接可能已經被限制創建新的流
releasedConnection = transmitter.connection
// 釋放當前連接的資源,如果該連接已經被限制創建新的流,就返回一個Socket以關閉連接
toClose = if (transmitter.connection != null && transmitter.connection!!.noNewExchanges) {
transmitter.releaseConnectionNoEvents()
} else {
null
}
if (transmitter.connection != null) {
// We had an already-allocated connection and it's good.
result = transmitter.connection
releasedConnection = null
}
if (result == null) {
// Attempt to get a connection from the pool.
//嘗試從連接池中獲取一個連接
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!!)
}
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.
// 根據一系列的 IP地址從連接池中獲取一個鏈接
routes = routeSelection!!.routes
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.
//在連接池中如果沒有該連接,則創建連接並立即將其分配。這使得異步cancel()可以中斷我們即將進行的握手
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!!
}
// Do TCP + TLS handshakes. This is a blocking operation.
// 進行 TCP 和 TLS 握手
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.
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 {
connectionPool.put(result!!)
transmitter.acquireConnectionNoEvents(result!!)
}
}
socket?.closeQuietly()
eventListener.connectionAcquired(call, result!!)
return result!!
}
再看下ConnectInterceptor連接攔截器的連接線程池是怎麼管理連接的
class RealConnectionPool(
taskRunner: TaskRunner,
private val maxIdleConnections: Int,
keepAliveDuration: Long,
timeUnit: TimeUnit
) {
private val cleanupQueue: TaskQueue = taskRunner.newQueue()
//2.連接隊列
private val cleanupTask = object : Task("OkHttp ConnectionPool") {
override fun runOnce() = cleanup(System.nanoTime())
}
//1. 往連接隊列中添加連接,並定時管理連接隊列
fun put(connection: RealConnection) {
this.assertThreadHoldsLock()
//往連接隊列中添加連接
connections.add(connection)
//並定時管理連接隊列
cleanupQueue.schedule(cleanupTask)
}
3. 管理連接隊列
fun cleanup(now: Long): Long {
var inUseConnectionCount = 0
var idleConnectionCount = 0
var longestIdleConnection: RealConnection? = null
var longestIdleDurationNs = Long.MIN_VALUE
// Find either a connection to evict, or the time that the next eviction is due.
synchronized(this) {
//遍歷隊列中的連接
for (connection in connections) {
// If the connection is in use, keep searching.
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++
continue
}
idleConnectionCount++
// If the connection is ready to be evicted, we're done.
//找到 可以被清理 + 閒置時間最長的連接
val idleDurationNs = now - connection.idleAtNanos
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs
longestIdleConnection = connection
}
}
when {
// maxIdleConnections 表示最大允許的閒置的連接的數量,
// keepAliveDurationNs表示連接允許存活的最長的時間
// 默認空閒連接最大數目爲5個,keepalive 時間最長爲5分鐘
longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections -> {
// We've found a connection to evict. Remove it from the list, then close it below
// (outside of the synchronized block).
// 該連接的時長超出了最大的活躍時長或者閒置的連接數量超出了最大允許的範圍,直接移除
connections.remove(longestIdleConnection)
if (connections.isEmpty()) cleanupQueue.cancelAll()
}
idleConnectionCount > 0 -> {
// A connection will be ready to evict soon.
// 閒置的連接的數量大於0,返回 等待下次清理的時間
return keepAliveDurationNs - longestIdleDurationNs
}
inUseConnectionCount > 0 -> {
// All connections are in use. It'll be at least the keep alive duration 'til we run
// again.
// 所有的連接都在使用中,5分鐘後再進行下一次清理
return keepAliveDurationNs
}
else -> {
// No connections, idle or in use.
// 沒有連接
return -1
}
}
}
longestIdleConnection!!.socket().closeQuietly()
// Cleanup again immediately.
return 0L
}
}
可以從上面看到:
-
首先會對緩存中的連接進行遍歷,以尋找一個閒置時間最長的可被清理的連接;
-
然後根據該連接的閒置時長和最大允許的連接數量等參數來決定是否應該清理該連接,同時注意上面的方法的返回值是一個時間;
- 如果該連接的時長超出了最大的活躍時長或者閒置的連接數量超出了最大允許的範圍,直接移除
- 如果閒置時間最長的連接仍然需要一段時間才能被清理的時候,會返回這段時間的時間差,然後會在這段時間之後再次對連接池進行清理
- 如果所有的連接都在使用中,5分鐘後再進行下一次清理
- 不存在連接,則返回-1