前言
OkHttp 是一款非常優(yōu)秀的網絡請求框架叫潦,隨著Kotlin語言的不斷完善星爪,OkHttp 自 4.0 版本開始使用Kotlin編寫牌柄,對于鞏固Kotlin語法知識及實踐蒋歌,研讀OkHttp 4.0及以上版本的源碼是一個不錯的選擇摧阅。
首先列出一些前置知識點:
1xx:信息汰蓉,請求收到,繼續(xù)處理
2xx:成功棒卷,行為被成功地接受顾孽、理解和采納
3xx:重定向祝钢,為了完成請求,必須進一步執(zhí)行的動作
4xx:客戶端錯誤若厚,請求包含語法錯誤或者請求無法實現(xiàn)
5xx:服務器錯誤拦英,服務器不能實現(xiàn)一種明顯無效的請求
OkHttp的基本使用
- 首先添加依賴庫(去官網找最新的或想要的版本)
- Http 請求有多種類型,常用的分為 Get 和 Post测秸,而 POST 又分為 Form 和 Multiple 等疤估,下面我們以Get請求為例:
// 1.創(chuàng)建OkHttpClient 對象,
// var client = OkHttpClient();//方式一
//方式二:
val client = OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build()
//2.創(chuàng)建請求對象并添加請求參數信息
val request = Request.Builder().url("").build()
//3.構建進行請求操作的call對象
val call = client.newCall(request)
//同步請求 Call (RealCall)—>execute() 返回response
// val response = client.newCall(request).execute()
//異步請求 Call (RealCall)—>enqueue()
call.enqueue(
object : Callback {
override fun onFailure(call: Call, e: IOException) {
println(e.stackTrace.toString())
}
@Throws(IOException::class)
override fun onResponse(call: Call, response: Response) {
println(response.body.toString())
}
})
- 首先使用OkHttpClint的構造
OkHttpClient()
或者Build模式構建一個OkHttpClint的對象實例霎冯; - 使用構建者模式構建一個Request對象铃拇,通過OkHttpClient和Request對象,構建出Call對象沈撞;
- 執(zhí)行call的
enqueue()
或者execute()
慷荔。
注意:在實際開發(fā)中建議將OkHttpClint對象的創(chuàng)建封裝成單列, 因為每個 OkHttpClient 對象都管理自己獨有的線程池和連接池关串,復用連接池和線程池能夠減少延遲拧廊、節(jié)省內存。
OkHttp 源碼分析
一. OkHttpClient
constructor() : this(Builder())
//這里是默認的參數設置
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()//調度器晋修,通過雙端隊列保存Calls(同步&異步Call)
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()//一個Call的狀態(tài)監(jiān)聽器
internal var retryOnConnectionFailure = true
internal var authenticator: Authenticator = Authenticator.NONE
internal var followRedirects = true
internal var followSslRedirects = true
internal var cookieJar: CookieJar = CookieJar.NO_COOKIES//默認沒有Cookie
internal var cache: Cache? = null
internal var dns: Dns = Dns.SYSTEM//域名解析系統(tǒng) domain name -> ip address
internal var proxy: Proxy? = null
internal var proxySelector: ProxySelector? = null//使用默認的代理選擇器
internal var proxyAuthenticator: Authenticator = Authenticator.NONE
internal var socketFactory: SocketFactory = SocketFactory.getDefault()//默認的Socket 工廠生產Socket
internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
internal var x509TrustManagerOrNull: X509TrustManager? = null
internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS//OKHttp連接(Connection)配置
internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
internal var certificateChainCleaner: CertificateChainCleaner? = null
internal var callTimeout = 0
internal var connectTimeout = 10_000
internal var readTimeout = 10_000
internal var writeTimeout = 10_000
internal var pingInterval = 0//和WebSocket有關,為了保持長連接吧碾,我們必須間隔一段時間發(fā)送一個ping指令進行保活
internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
internal var routeDatabase: RouteDatabase? = null
connectionSpecs: OKHttp連接(Connection)配置
companion object {
internal val DEFAULT_PROTOCOLS = immutableListOf(HTTP_2, HTTP_1_1)
internal val DEFAULT_CONNECTION_SPECS = immutableListOf(
ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT)
}
/**
* A modern TLS configuration that works on most client platforms and can connect to most servers.
* This is OkHttp's default configuration.
*/
//針對TLS的墓卦, 是OkHttp 的默認配置
@JvmField
val MODERN_TLS = Builder(true)
.cipherSuites(*APPROVED_CIPHER_SUITES)
.tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
.supportsTlsExtensions(true)
.build()
/** URL的未經加密倦春,未經身份驗證的連接 */
@JvmField
val CLEARTEXT = Builder(false).build()
二. 同步請求流程分析
以下代碼為同步請求流程中的核心代碼,按照調用次序呈現(xiàn)落剪。
// 1.
val response = client.newCall(request).execute()
// 2.
/** Prepares the [request] to be executed at some point in the future. */
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
//RealCall的execute()
// 3. 真正執(zhí)行請求的是在call的實現(xiàn)類 RealCall的execute()中
override fun execute(): Response {
//標記請求執(zhí)行狀態(tài):一個請求只能執(zhí)行一次
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
timeout.enter()
callStart()
try {
// 4. 通知dispatcher已經進入執(zhí)行狀態(tài)
client.dispatcher.executed(this)
// 5. 通過連接器的鏈式調用進行請求處理并返回最終響應結果
return getResponseWithInterceptorChain()
} finally {
// 6. 通知dispatcher自己已執(zhí)行完畢
client.dispatcher.finished(this)
}
}
//4 . dispatcher.executed()
/** Used by `Call#execute` to signal it is in-flight. */
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
//Dispatcher中維護的ArrayDeque
/** 準備執(zhí)行的異步請求隊列. */
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
/** 正在執(zhí)行的異步請求隊列 */
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
/** 正在執(zhí)行的同步請求隊列 */
private val runningSyncCalls = ArrayDeque<RealCall>()
//5 .RealCall的getResponseWithInterceptorChain()
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors//用戶在構建OkHttpClient是配置的連接器
interceptors += RetryAndFollowUpInterceptor(client)//負責請求失敗后的重試和重定向
interceptors += BridgeInterceptor(client.cookieJar)//對請求和響應的參數進行必要的處理
interceptors += CacheInterceptor(client.cache)//讀取緩存數據返回睁本、更新緩存
interceptors += ConnectInterceptor//負責跟服務器的鏈接操作
if (!forWebSocket) {
//創(chuàng)建OkHttpClient時設置的networkInterceptor
interceptors += client.networkInterceptors
}
//向服務器發(fā)送請求數據,讀取響應數據
interceptors += CallServerInterceptor(forWebSocket)
//將請求對象及OkHttpClient的一些配置封裝在RealInterceptorChain中
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 {
//開啟鏈式調用
val response = chain.proceed(originalRequest)
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
//7. RealInterceptorChain的proceed()
@Throws(IOException::class)
override fun proceed(request: Request): Response {
check(index < interceptors.size)
calls++
if (exchange != null) {
check(exchange.finder.sameHostAndPort(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
check(calls == 1) {
"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
}
}
// Call the next interceptor in the chain. 實例化下一個攔截器
val next = copy(index = index + 1, request = request)
//獲取當前攔截器
val interceptor = interceptors[index]
//調用當前攔截器的intercept(),并將下一個攔截器的RealIterceptorChain對象傳遞下去,最后返回響應結果
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
if (exchange != null) {
check(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. 異步請求 Call (RealCall)—>enqueue()
client.newCall(request).enqueue(
object : Callback {
override fun onFailure(call: Call, e: IOException) {
println(e.stackTrace.toString())
}
@Throws(IOException::class)
override fun onResponse(call: Call, response: Response) {
println(response.body.toString())
}
})
//2. RealCall 的enqueue()
override fun enqueue(responseCallback: Callback) {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
//3. dispatcher 的enqueue()
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
//將請求添加到等待執(zhí)行的異步請求隊列中
readyAsyncCalls.add(call)
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
/**
* Promotes eligible calls from [readyAsyncCalls] to [runningAsyncCalls] and runs them on the
* executor service. Must not be called with synchronization because executing calls can call
* into user code.
*
* @return true if the dispatcher is currently running calls.
*/
//4. 不斷從readyAsyncCalls中取出要執(zhí)行的請求放到runningAsyncCalls中忠怖,并將readyAsyncCalls中的移除
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
// 如果其中的runningAsynCalls不滿呢堰,且call占用的host小于最大數量,則將call加入到runningAsyncCalls中執(zhí)行凡泣,
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
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
// 利用線程池執(zhí)行call
asyncCall.executeOn(executorService)
}
return isRunning
}
異步請求的dispatcher.enqueue(AsyncCall)中傳入是call 是一個AsyncCall枉疼,接下來看AsyncCall的實現(xiàn).它是RealCall的內部類,實際是一個Runnable鞋拟。
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
@Volatile var callsPerHost = AtomicInteger(0)
private set
fun reuseCallsPerHostFrom(other: AsyncCall) {
this.callsPerHost = other.callsPerHost
}
val host: String
get() = originalRequest.url.host
val request: Request
get() = originalRequest
val call: RealCall
get() = this@RealCall
/**
* Attempt to enqueue this async call on [executorService]. This will attempt to clean up
* if the executor has been shut down by reporting the call as failed.
*/
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
//在線程遲中執(zhí)行
executorService.execute(this)
success = true
} catch (e: RejectedExecutionException) {
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
noMoreExchanges(ioException)
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
client.dispatcher.finished(this) // This call is no longer running!
}
}
}
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
timeout.enter()
try {
//最終進行攔截器的鏈式調用來處理請求并返回最終的響應結果
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()}", 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)
responseCallback.onFailure(this@RealCall, canceledException)
}
throw t
} finally {
client.dispatcher.finished(this)
}
}
}
}
通過源碼看到Dispatcher維護了三個ArrayDeque骂维,一個保存了正在執(zhí)行的同步任務;一個保存異步正在執(zhí)行的請求贺纲,另一個是異步等待執(zhí)行的請求航闺,異步右兩個ArrayDeque是因為Dispatcher默認支持最大的并發(fā)請求是64個,單個Host最多執(zhí)行5個并發(fā)請求,如果超過潦刃,則Call會先被放入到readyAsyncCall中侮措,當出現(xiàn)空閑的線程時,再將readyAsyncCall中的線程移入到runningAsynCalls中福铅,執(zhí)行請求萝毛。
通過攔截器鏈處理项阴,得到響應結果后執(zhí)行finally中的代碼
dispatcher.finished(this)
滑黔,
現(xiàn)在來看下這個方法,走到這环揽,一個請求流程就結束了略荡。
/** Used by [AsyncCall.run] to signal completion. */
//異步請求時調用
internal fun finished(call: AsyncCall) {
call.callsPerHost.decrementAndGet()
finished(runningAsyncCalls, call)
}
/** Used by [Call.execute] to signal completion. */
//同步請求時調用
internal fun finished(call: RealCall) {
finished(runningSyncCalls, call)
}
//最終都調用這個方法
private fun <T> finished(calls: Deque<T>, call: T) {
val idleCallback: Runnable?
synchronized(this) {
//將當前call從其隊列中移除
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
val isRunning = promoteAndExecute()
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
總結:網絡請求是從OkHttpClient().newCall(request)
開始的,通過創(chuàng)建的OkHttpClient對象和Request對象歉胶,構建出一個RealCall對象來執(zhí)行網絡請求汛兜,同步請求是在RealCall
的execute()
方法中,異步請求是在enqueue()
中通今,在這兩個方法中都用OkHttpClient
對象的dispatcher
執(zhí)行對應的請求方法粥谬。對于同步請求,dispatcher
的execute()
就是將請求加入到runningSyncCalls
這個雙端隊列中辫塌;對于異步請求漏策,dispatcher
進行請求的分發(fā)執(zhí)行。在dispatcher
將請求分發(fā)后調用getResponseWithInterceptorChain()
方法臼氨,在這里掺喻,==依次==將client.interceptorsRetryAndFollowUpInterceptor、BridgeInterceptor储矩、CacheInterceptor感耙、ConnectInterceptor、client.networkInterceptors和CallServerInterceptor
添加到一個集合中持隧,并創(chuàng)建出一個攔截器鏈RealInterceptorChain
即硼,通過RealInterceptorChain.proceed()
使每一個攔截器執(zhí)行完畢之后會調用下一個攔截器或者不調用并返回結果。顯然屡拨,我們最終拿到的響應就是這個鏈條執(zhí)行之后返回的結果只酥。
-
整體的請求流程圖如下:
image
四.OkHttp內置攔截器源碼分析
1. RetryAndFollowUpInterceptor
這個攔截器負責重試和重定向,當一個請求由于各種原因失敗了洁仗,如果是路由或者連接異常层皱,則嘗試恢復,否則赠潦,根據響應碼(ResponseCode),followup方法會對Request進行再處理以得到新的Request叫胖,然后沿著攔截器鏈繼續(xù)新的Request;當嘗試次數超過最大次數就拋出異常她奥。代碼邏輯相對比較簡單瓮增,這里就不貼出來了怎棱。
2. BridgeInterceptor
負責將用戶請求轉換為網絡請求,也就是根據 Request 信息組建請求 Header 以及設置響應數據绷跑,包括設置 Cookie 以及gzip拳恋。源碼就不貼出來了。
3. CacheInterceptor
負責根據請求的信息和緩存的響應的信息來判斷是否存在可用的緩存砸捏,讀取緩存直接返回谬运、否則就繼續(xù)使用責任鏈模式來從服務器中獲取響應。當獲取到響應的時候垦藏,更新緩存梆暖。
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//從緩存中獲取
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
//緩存策略,決定使用緩存還是從網絡獲取
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
//根據緩存策略掂骏,更新統(tǒng)計指標:請求次數轰驳、使用網絡請求次數、使用緩存次數
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.
//如果既無網絡請求可用级解,又沒有緩存,則返回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()
}
// 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.
//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()
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()
//允許緩存且請求結果不為空辛馆,則寫入緩存
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
}
// CacheStrategy中的核心方法 computeCandidate()
/** Returns a strategy to use assuming the request can use the network. */
private fun computeCandidate(): CacheStrategy {
// No cached response. 沒有緩存俺陋,直接進行網絡請求
if (cacheResponse == null) {
return CacheStrategy(request, null)
}
// Drop the cached response if it's missing a required handshake. 是https請求,但是沒有握手昙篙,進行網絡請求
if (request.isHttps && cacheResponse.handshake == null) {
return CacheStrategy(request, null)
}
// 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)
}
val requestCaching = request.cacheControl
//請求頭nocache或者請求頭包含If-Modified-Since或者If-None-Match(意味著本地緩存過期腊状,需要服務器驗證本地緩存是不是還能繼續(xù)使用)
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())
}
//緩存過期了,但仍然可用苔可,給相應頭中添加了Warning缴挖,使用緩存
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())//使用緩存
}
// 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?
//流程走到這,說明緩存已經過期了
//添加請求頭:If-Modified-Since或者If-None-Match
//etag與If-None-Match配合使用
//lastModified與If-Modified-Since配合使用
//前者和后者的值是相同的
//區(qū)別在于前者是響應頭焚辅,后者是請求頭映屋。
//后者用于服務器進行資源比對,看看是資源是否改變了同蜻。
// 如果沒有棚点,則本地的資源雖過期還是可以用的
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!!)
val conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build()
return CacheStrategy(conditionalRequest, cacheResponse)
}
從上述CatchStragety.computeCandidate()方法可知,緩存策略如下:
1. 沒有緩存湾蔓,直接網絡請求瘫析;
2. 如果是Https,沒有進行握手,則進行網絡請求贬循;
3. 設置了不可緩存咸包,則進行網絡請求;
4. 請求頭nocache或者請求頭包含If-Modified-Since或者If-None-Match杖虾,則需要服務器驗證本地緩存是不是還能繼續(xù)使用烂瘫,進行網絡請求;
5. 可以緩存奇适,并且緩存過期過期了但是還可以使用坟比,這時給響應頭添加Warning后,使用緩存滤愕;
6. 緩存已經過期温算,添加請求頭:If-Modified-Since或者If-None-Match,進行網絡請求间影;
整個CatcheIncepter的執(zhí)行依靠CatchStragety的緩存策略,代碼中添加了注釋茄茁,這里整理下流程如下:
1. 如果網絡不可用并且無可用的有效緩存魂贬,則返回504錯誤;
2. 如果禁止了網絡請求裙顽,則直接使用緩存付燥;
3. 如果沒有緩存且網絡請求可用,則進行網絡請求愈犹;
4. 如果此時有緩存键科,并且網絡請求返回HTTP_NOT_MODIFIED(304),說明緩存還是有效的漩怎,則合并網絡響應和緩存結果勋颖。同時更新緩存;
5. 如果沒有緩存勋锤,則將請求回來的結果寫入新的緩存中饭玲;
6. 返回響應數據。
可以看到叁执,緩存的獲取茄厘、添加、更新等操作都是在Catche中初始化了一個DiskLruCache來完成的谈宛,具體方法如下:
//獲取緩存
internal fun get(request: Request): Response? {
val key = key(request.url)
val snapshot: DiskLruCache.Snapshot = try {
cache[key] ?: return null
} catch (_: IOException) {
return null // Give up because the cache cannot be read.
}
val entry: Entry = try {
Entry(snapshot.getSource(ENTRY_METADATA))
} catch (_: IOException) {
snapshot.closeQuietly()
return null
}
val response = entry.response(snapshot)
if (!entry.matches(request, response)) {
response.body?.closeQuietly()
return null
}
return response
}
//添加緩存
internal fun put(response: Response): CacheRequest? {
val requestMethod = response.request.method
if (HttpMethod.invalidatesCache(response.request.method)) {
try {
remove(response.request)
} catch (_: IOException) {
// The cache cannot be written.
}
return null
}
if (requestMethod != "GET") {
// Don't cache non-GET responses. We're technically allowed to cache HEAD requests and some
// POST requests, but the complexity of doing so is high and the benefit is low.
return null
}
if (response.hasVaryAll()) {
return null
}
val entry = Entry(response)
var editor: DiskLruCache.Editor? = null
try {
editor = cache.edit(key(response.request.url)) ?: return null
entry.writeTo(editor)
return RealCacheRequest(editor)
} catch (_: IOException) {
abortQuietly(editor)
return null
}
}
//更新緩存
internal fun update(cached: Response, network: Response) {
val entry = Entry(network)
val snapshot = (cached.body as CacheResponseBody).snapshot
var editor: DiskLruCache.Editor? = null
try {
editor = snapshot.edit() ?: return // edit() returns null if snapshot is not current.
entry.writeTo(editor)
editor.commit()
} catch (_: IOException) {
abortQuietly(editor)
}
}
4. ConnectInterceptor
這個攔截器打開與目標服務器的鏈接并進入下一個攔截器次哈。
通過RealCall
的initExchange(chain)
創(chuàng)建一個Exchange
對象,并調用 Chain.proceed()
方法吆录。
initExchange()
方法中會先通過 ExchangeFinder
嘗試去 RealConnectionPool
中尋找已存在的連接窑滞,未找到則會重新創(chuàng)建一個RealConnection
并開始連接,然后將其存入RealConnectionPool
,現(xiàn)在已經準備好了RealConnection
對象葛假,然后通過請求協(xié)議創(chuàng)建不同的ExchangeCodec
并返回障陶,返回的ExchangeCodec
正是創(chuàng)建Exchange對象的一個參數。
- 下面說一下在建立連接過程中涉及到的幾個重要類:
Route:
是連接到服務器的具體路由聊训。其中包含了 IP 地址抱究、端口、代理等參數带斑。
由于存在代理或者 DNS 可能返回多個 IP 地址的情況鼓寺,所以同一個接口地址可能會對應多個 route
。
在創(chuàng)建 Connection 時將會使用 Route 而不是直接用 IP 地址勋磕。
RouteSelector:
路由選擇器妈候,其中存儲了所有可用的 route
,在準備連接時時會通過 RouteSelector.next()
方法獲取下一個 Route
挂滓。
值得注意的是苦银,RouteSelector
中包含了一個 routeDatabase
對象,其中存放著連接失敗的Route
赶站,RouteSelector
會將其中存儲的上次連接失敗的route
放在最后幔虏,以此提高連接速度。
RealConnection:
RealConnection
實現(xiàn)了 Connection
接口贝椿,其中使用 Socket
建立HTTP/HTTPS
連接,并且獲取 I/O 流想括,同一個 Connection
可能會承載多個 HTTP 的請求與響應。
RealConnectionPool:
這是用來存儲 RealConnection
的池子烙博,內部使用一個雙端隊列來進行存儲瑟蜈。
在 OkHttp 中,一個連接(RealConnection)用完后不會立馬被關閉并釋放掉渣窜,而且是會存儲到連接池(RealConnectionPool)中铺根。
除了緩存連接外,緩存池還負責定期清理過期的連接图毕,在 RealConnection 中會維護一個用來描述該連接空閑時間的字段夷都,每添加一個新的連接到連接池中時都會進行一次檢測,遍歷所有的連接予颤,找出當前未被使用且空閑時間最長的那個連接囤官,如果該連接空閑時長超出閾值,或者連接池已滿蛤虐,將會關閉該連接党饮。
ExchangeCodec:
ExchangeCodec
負責對Request
編碼及解碼 Response
,也就是寫入請求及讀取響應驳庭,我們的請求及響應數據都通過它來讀寫刑顺。其實現(xiàn)類有兩個:Http1ExchangeCodec
及 Http2ExchangeCodec
氯窍,分別對應兩種協(xié)議版本。
Exchange:
功能類似 ExchangeCodec
蹲堂,但它是對應的是單個請求狼讨,其在 ExchangeCodec
基礎上擔負了一些連接管理及事件分發(fā)的作用。
具體而言柒竞,Exchange
與 Request
一一對應政供,新建一個請求時就會創(chuàng)建一個 Exchange
,該 Exchange
負責將這個請求發(fā)送出去并讀取到響應數據朽基,而發(fā)送與接收數據使用的是 ExchangeCodec
输吏。
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.call.initExchange(chain)
val connectedChain = realChain.copy(exchange = exchange)
return connectedChain.proceed(realChain.request)
}
//RealCall 中的initExchange()初始化Exchange對象
/** Finds a new or pooled connection to carry a forthcoming request and response. */
internal fun initExchange(chain: RealInterceptorChain): Exchange {
synchronized(connectionPool) {
check(!noMoreExchanges) { "released" }
check(exchange == null)
}
val codec = exchangeFinder!!.find(client, chain)
val result = Exchange(this, eventListener, exchangeFinder!!, codec)
this.interceptorScopedExchange = result
synchronized(connectionPool) {
this.exchange = result
this.exchangeRequestDone = false
this.exchangeResponseDone = false
return result
}
}
//找到可用的resultConnection后根據協(xié)議創(chuàng)建ExchangeCodec并返回
fun find(
client: OkHttpClient,
chain: RealInterceptorChain
): ExchangeCodec {
try {
val resultConnection = findHealthyConnection(
connectTimeout = chain.connectTimeoutMillis,
readTimeout = chain.readTimeoutMillis,
writeTimeout = chain.writeTimeoutMillis,
pingIntervalMillis = client.pingIntervalMillis,
connectionRetryEnabled = client.retryOnConnectionFailure,
doExtensiveHealthChecks = chain.request.method != "GET"
)
return resultConnection.newCodec(client, chain)
} catch (e: RouteException) {
trackFailure(e.lastConnectException)
throw e
} catch (e: IOException) {
trackFailure(e)
throw RouteException(e)
}
}
//ExchangeFinder的findConnection方法中找已經存在的可用的鏈接
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
*/
@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) {
…………
// Attempt to get a connection from the pool.
//從connectPool中找可用的鏈接并返回
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
foundPooledConnection = true
result = call.connection
} else if (nextRouteToTry != null) {
selectedRoute = nextRouteToTry
nextRouteToTry = null
}
}
}
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!!
}
…………
// 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.
//創(chuàng)建一個新的RealConnection
result = RealConnection(connectionPool, selectedRoute!!)
connectingConnection = result
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.callAcquirePooledConnection(address, call, routes, true)) {
// We lost the race! Close the connection we created and return the pooled connection.
result!!.noNewExchanges = true
socket = result!!.socket()
result = call.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!!)//將新創(chuàng)建的RealConnection添加到connectPool中
call.acquireConnectionNoEvents(result!!)
}
}
socket?.closeQuietly()
eventListener.connectionAcquired(call, result!!)
return result!!
}
5. CallServerInterceptor
這是OkHttp 的連接器鏈中的最后一個攔截器巍佑,負責利用exchange把Request中的數據發(fā)送給服務端乱投,并獲取到數據寫入到Response中选调。
到這里,OkHttp框架的核心邏輯已經梳理完了霎俩,回顧一下整體的架構實現(xiàn)哀军,用到的設計模式有:Builder模式(OKHttpClient的構建)、工廠方法模式(Call接口提供了內部接口Factory茸苇、責任鏈模式(攔截器鏈)排苍、享元模式(在Dispatcher的線程池)、策略模式(CacheInterceptor中數據選擇等学密。
參考資源
- OkHttp 4.6源碼
- okhttp源碼解析
- OkHttp 源碼分析
- 感謝您閱讀這篇文章,若有不正確的地方传藏,歡迎指正!