\\ //
\\ .ooo. //
.@@@@@@@@@.
:@@@@@@@@@@@@@:
:@@. '@@@@@' .@@:
@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@
:@@ :@@@@@@@@@@@@@@@@@. @@:
@@@ '@@@@@@@@@@@@@@@@@, @@@ @@@ '@@@@@@@@@@@@@@@@@, @@@
@@@ '@@@@@@@@@@@@@@@@@, @@@ @@@ '@@@@@@@@@@@@@@@@@, @@@
@@@ '@@@@@@@@@@@@@@@@@, @@@ @@@ '@@@@@@@@@@@@@@@@@, @@@
@@@@@@@@@@@@@@@@@
'@@@@@@@@@@@@@@@'
@@@@ @@@@
@@@@ @@@@
@@@@ @@@@
'@@' '@@'
:@@@.
.@@@@@@@: +@@ `@@ @@` @@ @@
.@@@@'@@@@: +@@ `@@ @@` @@ @@ @@@ @@@ +@@ `@@ @@` @@ @@ .@@ @@: +@@ @@@ `@@ @@` @@@@@@ @@@@@@ @@;@@@@@ @@@ @@@ +@@ @@@ `@@ @@` @@@@@@ @@@@@@ @@@@@@@@@ @@@ @@@ +@@ @@@ `@@@@@@@@@@` @@ @@ @@@ :@@ @@@ @@@ +@@@@@ `@@@@@@@@@@` @@ @@ @@# @@+ @@@ @@@ +@@@@@+ `@@ @@` @@ @@ @@: @@# @@: .@@` +@@@+@@ `@@ @@` @@ @@ @@# @@+ @@@. .@@@ +@@ @@@ `@@ @@` @@ @@ @@@ ,@@ @@@@@@@@@ +@@ @@@ `@@ @@` @@@@ @@@@ @@@@#@@@@ @@@@@@@ +@@ #@@ `@@ @@` @@@@: @@@@: @@'@@@@@
@@:
@@:
@@:
複製代碼
對於 Android Developer 來講,不少開源庫都是屬於開發必備的知識點,從使用方式到實現原理再到源碼解析,這些都須要咱們有必定程度的瞭解和運用能力。因此我打算來寫一系列關於開源庫源碼解析和實戰演練的文章,初定的目標是 EventBus、ARouter、LeakCanary、Retrofit、Glide、OkHttp、Coil 等七個知名開源庫,但願對你有所幫助 😇😇java
公衆號:字節數組git
系列文章導航:github
本文基於當前 OkHttp 的最新版本進行講解。值得一提的是,OkHttp 和 OkIO 目前已經被官方用 Kotlin 語言重寫了一遍,因此還沒學 Kotlin 的同窗可能連源碼都比較難看懂了,Kotlin 入門能夠看個人這篇文章:兩萬六千字帶你 Kotlin 入門數組
dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
}
複製代碼
先來看一個小例子,後面的講解都會基於這個例子涉及到的模塊來展開緩存
/** * 做者:leavesC * 時間:2020/11/10 22:11 * 描述: * GitHub:https://github.com/leavesC */
const val URL = "https://publicobject.com/helloworld.txt"
fun main() {
val okHttClient = OkHttpClient.Builder()
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(10))
.writeTimeout(Duration.ofSeconds(10))
.retryOnConnectionFailure(true)
.build()
val request = Request.Builder().url(URL).build()
val call = okHttClient.newCall(request)
val response = call.execute()
println(response.body?.string())
}
複製代碼
以上代碼就完成了一次 Get 請求,其包含的操做有:安全
OkHttpClient 使用了 Builder 模式來完成初始化,其提供了不少的配置參數,每一個選項都有默認值,但大多數時候咱們仍是須要來進行自定義,因此也有必要來了解下其包含的全部參數服務器
class Builder constructor() {
//調度器
internal var dispatcher: Dispatcher = Dispatcher()
//鏈接池
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()
//鏈接失敗的時候是否重試
internal var retryOnConnectionFailure = true
//源服務器身份驗證
internal var authenticator: Authenticator = Authenticator.NONE
//是否容許重定向
internal var followRedirects = true
//是否容許ssl重定向
internal var followSslRedirects = true
//Cookie
internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
//緩存
internal var cache: Cache? = null
//DNS
internal var dns: Dns = Dns.SYSTEM
//代理
internal var proxy: Proxy? = null
//代理選擇器
internal var proxySelector: ProxySelector? = null
//代理身份驗證
internal var proxyAuthenticator: Authenticator = Authenticator.NONE
//Socket工廠
internal var socketFactory: SocketFactory = SocketFactory.getDefault()
//安全套接層
internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
internal var x509TrustManagerOrNull: X509TrustManager? = null
internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
//HTTP 協議
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
//ping 之間的時間間隔
internal var pingInterval = 0
internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
internal var routeDatabase: RouteDatabase? = null
}
複製代碼
Request 包含了網絡請求時的全部請求參數,一共包含如下五個:markdown
internal var url: HttpUrl? = null
internal var method: String
internal var headers: Headers.Builder
internal var body: RequestBody? = null
/** A mutable map of tags, or an immutable empty map if we don't have any. */
internal var tags: MutableMap<Class<*>, Any> = mutableMapOf()
複製代碼
當調用 okHttClient.newCall(request)
時就會獲得一個 Call 對象cookie
/** Prepares the [request] to be executed at some point in the future. */
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
複製代碼
Call 是一個接口,咱們能夠將其看作是網絡請求的啓動器,可用於發起同步請求或異步請求,但重複發起屢次請求的話會拋出異常網絡
interface Call : Cloneable {
//返回本次網絡請求的 Request 對象
fun request(): Request
//發起同步請求,可能會拋出異常
@Throws(IOException::class)
fun execute(): Response
//發起異步請求,經過 Callback 來回調最終結果
fun enqueue(responseCallback: Callback)
//取消網絡請求
fun cancel()
//是否已經發起過請求
fun isExecuted(): Boolean
//是否已經取消請求
fun isCanceled(): Boolean
//超時計算
fun timeout(): Timeout
//同個 Call 不容許重複發起請求,想要再次發起請求能夠經過此方法獲得一個新的 Call 對象
public override fun clone(): Call
fun interface Factory {
fun newCall(request: Request): Call
}
}
複製代碼
newCall
方法返回的實際類型是 RealCall,它是 Call 接口的惟一實現類
當咱們調用 execute
方法發起同步請求時,其主要邏輯是:
getResponseWithInterceptorChain
方法獲得 Response 對象class RealCall(
val client: OkHttpClient,
/** The application's original request unadulterated by redirects or auth headers. */
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
callStart()
try {
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
}
複製代碼
從上面的分析能夠看出來,getResponseWithInterceptorChain
方法就是重頭戲了,其返回了咱們最終獲得的 Response。但這裏先不介紹該方法,先來看看 Dispatcher 的邏輯
Dispatcher 是一個調度器,用於對全局的網絡請求進行緩存調度,其包含如下幾個成員變量
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>()
複製代碼
客戶端不該該無限制地同時發起多個網絡請求,由於除了網絡資源所限外,系統資源也是有限的,每一個請求都須要由一個線程來執行,而系統支持併發執行的線程數量是有限的,因此 OkHttp 內部就使用 maxRequests 來控制同時執行異步請求的最大線程數。此外,OkHttp 爲了提升效率,容許多個指向同一 host 的網絡請求共享同一個 Socket,而最大共享數量即 maxRequestsPerHost
爲了統計以上兩個運行參數,就須要使用 readyAsyncCalls、runningAsyncCalls 和 runningSyncCalls 來保存當前正在執行或者準備執行的網絡請求。runningSyncCalls 用於保存當前正在執行的同步任務,其存儲的是 RealCall。readyAsyncCalls 和 runningAsyncCalls 用於保存異步任務,其存儲的是 AsyncCall
RealCall 的 execute()
方法在開始請求前,會先將自身傳給 dispatcher,在請求結束後又會從 dispatcher 中移除
class RealCall(
val client: OkHttpClient,
/** The application's original request unadulterated by redirects or auth headers. */
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
callStart()
try {
//添加到 dispatcher
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
//從 dispatcher 中移除
client.dispatcher.finished(this)
}
}
}
複製代碼
Dispatcher 內部也只是相應的將 RealCall 添加到 runningSyncCalls 中或者是將其從 runningSyncCalls 中移除,保存到 runningSyncCalls 的目的是爲了方便統計當前全部正在運行的請求總數以及可以取消全部請求。此外,因爲同步請求會直接運行在調用者所在線程上,因此同步請求並不會受 maxRequests 的限制
class Dispatcher constructor() {
/** Used by [Call.execute] to signal it is in-flight. */
@Synchronized
internal fun executed(call: RealCall) {
runningSyncCalls.add(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) {
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
//判斷是否有須要處理的網絡請求
val isRunning = promoteAndExecute()
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
}
複製代碼
RealCall 的 enqueue
方法會將外部傳入的 Callback 包裝爲一個 AsyncCall 對象後傳給 dispatcher
class RealCall(
val client: OkHttpClient,
/** The application's original request unadulterated by redirects or auth headers. */
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
}
複製代碼
因爲 enqueue
對應的是異步請求,因此 OkHttp 內部就須要本身構造一個線程來執行請求,在請求結束後再經過 Callback 來將結果值回調給外部,異步請求邏輯對應的載體就是 AsyncCall 這個類
AsyncCall 是 RealCall 的非靜態內部類,因此 AsyncCall 能夠訪問到 RealCall 的全部變量和方法。此外,AsyncCall 繼承了 Runnable 接口,其 executeOn
方法就用於傳入一個線程池對象來執行run
方法。run
方法內仍是調用了 getResponseWithInterceptorChain()
方法來獲取 response,並經過 Callback 來將執行結果(無論成功仍是失敗)回調出去,在請求結束後也會將自身從 dispatcher 中移除
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
@Volatile var callsPerHost = AtomicInteger(0)
private set
fun reuseCallsPerHostFrom(other: AsyncCall) {
this.callsPerHost = other.callsPerHost
}
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)
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 在拿到 AsyncCall 對象後,會先將其存到 readyAsyncCalls 中,而後經過 findExistingCallWithHost
方法來查找當前是否有指向同一 Host 的異步請求,有的話則交換 callsPerHost 變量,該變量就用於標記當前指向同一 Host 的請求數量,最後調用 promoteAndExecute
方法來判斷當前是否容許發起請求
class Dispatcher constructor() {
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.call.forWebSocket) {
//查找當前是否有指向同一 Host 的異步請求
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
private fun findExistingCallWithHost(host: String): AsyncCall? {
for (existingCall in runningAsyncCalls) {
if (existingCall.host == host) return existingCall
}
for (existingCall in readyAsyncCalls) {
if (existingCall.host == host) return existingCall
}
return null
}
}
複製代碼
因爲當前正在執行的網絡請求總數可能已經達到限制,或者是指向同一 Host 的請求也達到限制了,因此 promoteAndExecute()
方法就用於從待執行列表 readyAsyncCalls 中獲取當前符合運行條件的全部請求,將請求存到 runningAsyncCalls 中,並調用線程池來執行
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()
//若是當前正在執行的異步請求總數已經超出限制,則直接返回
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
//若是指向同個 Host 的請求總數已經超出限制,則取下一個請求
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
i.remove()
//將 callsPerHost 遞增長一,表示指向該 Host 的連接數加一了
asyncCall.callsPerHost.incrementAndGet()
//將 asyncCall 存到可執行列表中
executableCalls.add(asyncCall)
//將 asyncCall 存到正在執行列表中
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
//執行全部符合條件的請求
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
複製代碼
上面有講到,三種請求的存儲容器是 ArrayDeque。ArrayDeque 屬於非線程安全的雙端隊列,因此涉及到多線程操做時都須要外部主動線程同步。那麼讓咱們想想,OkHttp 選擇 ArrayDeque 做爲任務容器的理由是什麼?以我粗淺的眼光來看,有如下幾點:
OkHttp 的異步請求是交由其內部的線程池來完成的,該線程池就長這樣:
private var executorServiceOrNull: ExecutorService? = null
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
}
return executorServiceOrNull!!
}
複製代碼
該線程池的參數設置有什麼優點呢?以我粗淺的眼光來看,有如下兩點:
雖然線程池自己對於最大線程數幾乎沒有限制,可是因爲提交任務的操做還受 maxRequests 的控制,因此實際上該線程池最多同時運行 maxRequests 個線程
既然 OkHttp 內部的線程池是不可能無限制地新建線程來執行請求的,那麼當請求總數已達到 maxRequests 後,後續的請求只能是先處於等待狀態,那麼這些等待狀態的請求會在何時被啓動呢?
同步請求和異步請求結束後都會調用到 Dispatcher 的兩個 finished
方法,在這兩個方法裏又會觸發到 promoteAndExecute()
方法去遍歷任務列表來執行,此時就推進了待處理列表的任務執行操做。因此說,Dispatcher 中的請求均可以看作是在自發性地啓動,每一個請求結束都會自動觸發下一個請求執行(若是有的話),省去了多餘的定時檢查這類操做
/** 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) {
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
//判斷當前是否有能夠啓動的待執行任務,有的話則啓動
val isRunning = promoteAndExecute()
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
複製代碼
getResponseWithInterceptorChain()
方法來拿到 Response 的重點再來看 getResponseWithInterceptorChain()
方法,其主要邏輯就是經過攔截器來完成整個網絡請求過程。在該方法中,除了會獲取外部主動設置的攔截器外,也會默認添加如下幾個攔截器
最後,request 和 interceptors 會用來生成一個 RealInterceptorChain 對象,由其來最終返回 response
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
//添加開發者設置的攔截器
interceptors += client.interceptors
//添加默認的攔截器
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
//若是不是 WebSocket 的話,那就再添加開發者設置的 NetworkInterceptors
interceptors += client.networkInterceptors
}
//CallServerInterceptor 是實際上發起網絡請求的地方
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
var calledNoMoreExchanges = false
try {
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)
}
}
}
複製代碼
Interceptor 是 OkHttp 裏很重要的一環,OkHttp 也是靠此爲開發者提供了很高的自由度。Interceptor 接口自己只包含一個 intercept
方法,在此方法內可拿到原始的 Request 對象以及最終的 Response
fun interface Interceptor {
@Throws(IOException::class)
fun intercept(chain: Chain): Response
}
複製代碼
例如,咱們能夠自定義一個 LogInterceptor 來打印網絡請求的請求參數以及最終的返回值
class LogInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
println(request)
val response = chain.proceed(request)
println(response)
return response
}
}
複製代碼
Interceptor 的實現初衷是爲了給開發者提供一個能夠控制網絡請求的發起過程及收尾工做的入口,例如**添加 header、日誌記錄、請求攔截、ResponseBody修改 **等,每一個 Interceptor 只負責本身關心的操做,那麼勢必就會有添加多個 Interceptor 的需求
咱們知道,只有讓每一個 Interceptor 都依次處理完 request 以後,OkHttp 才能根據最終的 request 對象去聯網請求獲得 response,因此每一個 Interceptor 須要依次拿到 request 進行自定義處理。請求到 response 後,Interceptor 可能還須要對 response 進行處理,那麼就還須要將 response 再依次傳遞給每一個 Interceptor。那麼,怎麼實現將多個 Interceptor 給串聯起來呢?
這裏來看一個簡化版本的 Interceptor 實現思路
假設咱們本身定義的 Interceptor 實現類有兩個:LogInterceptor 和 HeaderInterceptor,這裏只是簡單地將獲取到 request 和 response 的時機給打印出來,重點是要看每一個 Interceptor 的前後調用順序。爲了將兩個 Interceptor 給串聯起來,RealInterceptorChain 會循環獲取 index 指向的下一個 Interceptor 對象,每次構建一個新的 RealInterceptorChain 對象做爲參數來調用 intercept
方法,這樣外部只須要調用一次 realInterceptorChain.proceed
就能夠拿到最終的 response 對象
/** * 做者:leavesC * 時間:2020/11/11 16:08 * 描述: */
class Request
class Response
interface Chain {
fun request(): Request
fun proceed(request: Request): Response
}
interface Interceptor {
fun intercept(chain: Chain): Response
}
class RealInterceptorChain(
private val request: Request,
private val interceptors: List<Interceptor>,
private val index: Int
) : Chain {
private fun copy(index: Int): RealInterceptorChain {
return RealInterceptorChain(request, interceptors, index)
}
override fun request(): Request {
return request
}
override fun proceed(request: Request): Response {
val next = copy(index = index + 1)
val interceptor = interceptors[index]
val response = interceptor.intercept(next)
return response
}
}
class LogInterceptor : Interceptor {
override fun intercept(chain: Chain): Response {
val request = chain.request()
println("LogInterceptor -- getRequest")
val response = chain.proceed(request)
println("LogInterceptor ---- getResponse")
return response
}
}
class HeaderInterceptor : Interceptor {
override fun intercept(chain: Chain): Response {
val request = chain.request()
println("HeaderInterceptor -- getRequest")
val response = chain.proceed(request)
println("HeaderInterceptor ---- getResponse")
return response
}
}
fun main() {
val interceptorList = mutableListOf<Interceptor>()
interceptorList.add(LogInterceptor())
interceptorList.add(HeaderInterceptor())
val request = Request()
val realInterceptorChain = RealInterceptorChain(request, interceptorList, 0)
val response = realInterceptorChain.proceed(request)
println("main response")
}
複製代碼
上面的代碼看着思路還能夠,但是當運行後就會發現拋出了 IndexOutOfBoundsException,由於代碼裏並無對 index 進行越界判斷。並且,上面的代碼也缺乏了一個真正的生成 Response 對象的地方,每一個 Interceptor 只是在進行中轉調用而已,所以還須要一個來真正地完成網絡請求並返回 Response 對象的地方,即 CallServerInterceptor
因此,CallServerInterceptor 的intercept
方法就用來真正地執行網絡請求並生成 Response 對象,在這裏就不能再調用 proceed
方法了,且 CallServerInterceptor 須要放在 interceptorList 的最後一位
class CallServerInterceptor : Interceptor {
override fun intercept(chain: Chain): Response {
val request = chain.request()
println("CallServerInterceptor -- getRequest")
val response = Response()
println("CallServerInterceptor ---- getResponse")
return response
}
}
fun main() {
val interceptorList = mutableListOf<Interceptor>()
interceptorList.add(LogInterceptor())
interceptorList.add(HeaderInterceptor())
interceptorList.add(CallServerInterceptor())
val request = Request()
val realInterceptorChain = RealInterceptorChain(request, interceptorList, 0)
val response = realInterceptorChain.proceed(request)
println("main response")
}
複製代碼
最終的運行結果以下所示,能夠看出來,intercept
方法是根據添加順序來調用,而 response 是按照反方向來傳遞
LogInterceptor -- getRequest
HeaderInterceptor -- getRequest
CallServerInterceptor -- getRequest
CallServerInterceptor ---- getResponse
HeaderInterceptor ---- getResponse
LogInterceptor ---- getResponse
main response
複製代碼
以上代碼我簡化了 OkHttp 在實現 RealInterceptorChain 時的思路,其本質上就是經過將多個攔截器以責任鏈的方式來一層層調用,上一個攔截器處理完後將就將結果傳給下一個攔截器,直到最後一個攔截器(即 CallServerInterceptor )處理完後將 Response 再一層層往上傳遞
class RealInterceptorChain(
internal val call: RealCall,
private val interceptors: List<Interceptor>,
private val index: Int,
internal val exchange: Exchange?,
internal val request: Request,
internal val connectTimeoutMillis: Int,
internal val readTimeoutMillis: Int,
internal val writeTimeoutMillis: Int
) : Interceptor.Chain {
internal fun copy( index: Int = this.index, exchange: Exchange? = this.exchange, request: Request = this.request, connectTimeoutMillis: Int = this.connectTimeoutMillis, readTimeoutMillis: Int = this.readTimeoutMillis, writeTimeoutMillis: Int = this.writeTimeoutMillis ) = RealInterceptorChain(call, interceptors, index, exchange, request, connectTimeoutMillis,
readTimeoutMillis, writeTimeoutMillis)
@Throws(IOException::class)
override fun proceed(request: Request): Response {
···
val next = copy(index = index + 1, request = request)
val interceptor = interceptors[index]
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
···
return response
}
}
複製代碼
咱們在構建 OkHttpClient 的時候,添加攔截器的方法分爲兩類:addInterceptor
和addNetworkInterceptor
val okHttClient = OkHttpClient.Builder()
.addInterceptor { chain ->
chain.proceed(chain.request())
}
.addNetworkInterceptor { chain ->
chain.proceed(chain.request())
}
.build()
複製代碼
Interceptor 和 NetworkInterceptor 分別被稱爲應用攔截器和網絡攔截器,那麼它們有什麼區別呢?
前面有講到,OkHttp 在執行攔截器的時候,是按照以下順序的,這個順序就已經決定了不一樣攔截器的調用時機差別
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)
複製代碼
借用官方的一張圖片來表示
這裏能夠根據 square 官方提供的一個例子,來實如今下載一張 10 MB 圖片的時候經過攔截器對下載進度進行監聽,並同時把圖片下載到系統的桌面
實現思路就是對原始的 ResponseBody 進行多一層代理,計算已經從網絡中讀取到的字節數和資源的 contentLength 之間的百分比,從而獲得下載進度。此外,由於該攔截器是和確切的網絡請求相關,因此應該要設爲網絡攔截器才比較合理
/** * 做者:leavesC * 時間:2020/11/14 15:49 * 描述: * GitHub:https://github.com/leavesC */
fun main() {
run()
}
interface ProgressListener {
fun update(bytesRead: Long, contentLength: Long, done: Boolean)
}
private fun run() {
val request = Request.Builder()
.url("https://images.pexels.com/photos/5177790/pexels-photo-5177790.jpeg")
.build()
val progressListener: ProgressListener = object : ProgressListener {
var firstUpdate = true
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
if (done) {
println("completed")
} else {
if (firstUpdate) {
firstUpdate = false
if (contentLength == -1L) {
println("content-length: unknown")
} else {
System.out.format("content-length: %d\n", contentLength)
}
}
println(bytesRead)
if (contentLength != -1L) {
System.out.format("%d%% done\n", 100 * bytesRead / contentLength)
}
}
}
}
val client = OkHttpClient.Builder()
.addNetworkInterceptor { chain: Interceptor.Chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
.body(ProgressResponseBody(originalResponse.body!!, progressListener))
.build()
}
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw IOException("Unexpected code $response")
}
val desktopDir = FileSystemView.getFileSystemView().homeDirectory
val imageFile = File(desktopDir, "${System.currentTimeMillis()}.jpeg")
imageFile.createNewFile()
//讀取 InputStream 寫入到圖片文件中
response.body!!.byteStream().copyTo(imageFile.outputStream())
}
}
private class ProgressResponseBody constructor(
private val responseBody: ResponseBody,
private val progressListener: ProgressListener
) : ResponseBody() {
private var bufferedSource: BufferedSource? = null
override fun contentType(): MediaType? {
return responseBody.contentType()
}
override fun contentLength(): Long {
return responseBody.contentLength()
}
override fun source(): BufferedSource {
if (bufferedSource == null) {
bufferedSource = source(responseBody.source()).buffer()
}
return bufferedSource!!
}
private fun source(source: Source): Source {
return object : ForwardingSource(source) {
var totalBytesRead = 0L
@Throws(IOException::class)
override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead = super.read(sink, byteCount)
// read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytesRead += if (bytesRead != -1L) bytesRead else 0
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
return bytesRead
}
}
}
}
複製代碼
進度輸出就相似於:
content-length: 11448857
467
0% done
1836
0% done
3205
···
99% done
11442570
99% done
11448857
100% done
completed
複製代碼
關於 OkHttp 的源碼講解到這裏就結束了,但本文還缺乏了對 ConnectInterceptor 和 CallServerInterceptor 的講解,這二者是 OkHttp 完成實際網絡請求的地方,涉及到了 Connection 和 Socket 這些比較底層的領域,我無法講得多清晰,就直接略過這塊內容了~~
OkHttp 的運行效率很高,但在使用上仍是比較原始,通常咱們仍是須要在 OkHttp 之上進行一層封裝,Retrofit 就是一個對 OkHttp 的優秀封裝庫,對 Retrofit 的源碼講解能夠看個人這篇文章:三方庫源碼筆記(7)-Retrofit 源碼詳解
下篇文章就來寫關於 OkHttp 攔截器的實戰內容吧