說到源碼,不少朋友都以爲複雜,難理解。html
可是,若是是一個結構清晰且徹底解耦的優質源碼庫呢?web
OkHttp
就是這樣一個存在,對於這個原生網絡框架,想必你們也看過不少不少相關的源碼解析了。設計模式
它的源碼好看,易讀,清晰,因此今天我準備從設計模式的角度再來讀一遍 OkHttp
的源碼。緩存
主要內容就分爲兩類:服務器
(本文源碼版本爲okhttp:4.9.0,攔截器會放到下期再講)websocket
讀源碼,首先就要從它的使用方法開始:cookie
val okHttpClient = OkHttpClient() val request: Request = Request.Builder() .url(url) .build() okHttpClient.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { Log.d(TAG, "onFailure: ") } override fun onResponse(call: Call, response: Response) { Log.d(TAG, "onResponse: " + response.body?.string()) } })
從這個使用方法來看,我抽出了四個重要信息:網絡
大致意思咱們能夠先猜猜看:架構
配置一個客戶端實例okHttpClient
和一個Request請求
,而後這個請求經過okHttpClient
的newCall
方法封裝,最後用enqueue
方法發送出去,並收到Callback
響應。框架
接下來就一個個去認證,並找找其中的設計模式。
首先看看這個okhttp
的客戶端對象,也就是okHttpClient
。
OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor()) .readTimeout(500, TimeUnit.MILLISECONDS) .build();
在這裏,咱們實例化了一個HTTP的客戶端client,而後配置了它的一些參數,好比攔截器、超時時間
。
這種咱們經過一個統一的對象,調用一個接口或方法,就能完成咱們的需求,而起內部的各類複雜對象的調用和跳轉都不須要咱們關心的設計模式就是外觀模式(門面模式)
。
外觀模式(Facade Pattern)隱藏系統的複雜性,並向客戶端提供了一個客戶端能夠訪問系統的接口。這種類型的設計模式屬於結構型模式,它向現有的系統添加一個接口,來隱藏系統的複雜性。
其重點就在於系統內部和各個子系統之間的複雜關係咱們不須要了解,只須要去差遣這個門面
就能夠了,在這裏也就是OkHttpClient
。
它的存在就像一個接待員,咱們告訴它咱們的需求,要作的事情。而後接待員去內部處理,各類調度,最終完成。
外觀模式
主要解決的就是下降訪問複雜系統的內部子系統時的複雜度,簡化客戶端與之的接口。
這個模式也是三方庫很經常使用的設計模式,給你一個對象,你只須要對這個對象使喚,就能夠完成需求。
固然,這裏還有一個比較明顯的設計模式是建造者模式
,下面會說到。
val request: Request = Request.Builder() .url(url) .build() //Request.kt open class Builder { internal var url: HttpUrl? = null internal var method: String internal var headers: Headers.Builder internal var body: RequestBody? = null constructor() { this.method = "GET" this.headers = Headers.Builder() } open fun build(): Request { return Request( checkNotNull(url) { "url == null" }, method, headers.build(), body, tags.toImmutableMap() ) } }
從Request
的生成代碼中能夠看到,用到了其內部類Builder
,而後經過Builder
類組裝出了一個完整的有着各類參數的Request類
。
這也就是典型的 建造者(Builder)模式
。
建造者(Builder)模式,將一個複雜的對象的構建與它的表示分離,是的一樣的構建過程能夠建立不一樣的表示。
咱們能夠經過Builder
,構建了不一樣的Request
請求,只須要傳入不一樣的請求地址url,請求方法method,頭部信息headers,請求體body
便可。
(這也就是網絡請求中的請求報文的格式)
這種能夠經過構建造成不一樣的表示的 設計模式 就是 建造者模式
,也是用的不少,主要爲了方便咱們傳入不一樣的參數進行構建對象。
又好比上面okHttpClient
的構建。
接下來是調用OkHttpClient
類的newCall
方法獲取一個能夠去調用enqueue
方法的接口。
//使用 val okHttpClient = OkHttpClient() okHttpClient.newCall(request) //OkHttpClient.kt open class OkHttpClient internal constructor(builder: Builder) : Cloneable, Call.Factory, WebSocket.Factory { override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false) } //Call接口 interface Call : Cloneable { fun execute(): Response fun enqueue(responseCallback: Callback) fun interface Factory { fun newCall(request: Request): Call } }
newCall
方法,實際上是Call.Factory
接口裏面的方法。
也就是建立Call
的過程,是經過Call.Factory
接口的newCall
方法建立的,而真正實現這個方法交給了這個接口的子類OkHttpClient
。
那這種定義了統一建立對象
的接口,而後由子類來決定實例化這個對象的設計模式就是 工廠模式
。
在工廠模式中,咱們在建立對象時不會對客戶端暴露建立邏輯,而且是經過使用一個共同的接口來指向新建立的對象。
固然,okhttp
這裏的工廠有點小,只有一條生產線,就是Call接口
,並且只有一個產品,RealCall
。
接下來這個方法enqueue
,確定就是okhttp源碼
的重中之重了,剛纔說到newCall方法實際上是獲取了RealCall
對象,因此就走到了RealCall的enqueue
方法:
override fun enqueue(responseCallback: Callback) { client.dispatcher.enqueue(AsyncCall(responseCallback)) }
再轉向dispatcher。
//Dispatcher.kt val executorService: ExecutorService get() { if (executorServiceOrNull == null) { executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS, SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false)) } return executorServiceOrNull!! } internal fun enqueue(call: AsyncCall) { promoteAndExecute() } private fun promoteAndExecute(): Boolean { //經過線程池切換線程 for (i in 0 until executableCalls.size) { val asyncCall = executableCalls[i] asyncCall.executeOn(executorService) } return isRunning } //RealCall.kt fun executeOn(executorService: ExecutorService) { try { executorService.execute(this) success = true } }
這裏用到了一個新的類Dispatcher
,調用到的方法是asyncCall.executeOn(executorService)
這個executorService
參數你們應該都熟悉吧,線程池。最後是調用executorService.execute
方法執行線程池任務。
而線程池的概念其實也是用到了一種設計模式,叫作享元模式
。
享元模式(Flyweight Pattern)主要用於減小建立對象的數量,以減小內存佔用和提升性能。這種類型的設計模式屬於結構型模式,它提供了減小對象數量從而改善應用所需的對象結構的方式。
其核心就在於共享對象
,全部不少的池類對象,好比線程池、鏈接池等都是採用了享元模式 這一設計模式。固然,okhttp
中不止是有線程池,還有鏈接池
提供鏈接複用,管理全部的socket鏈接。
再回到Dispatcher
,因此這個類是幹嗎的呢?就是切換線程用的,由於咱們調用的enqueue
是異步方法,因此最後會用到線程池切換線程,執行任務。
繼續看看execute(this)
中的this任務。
override fun run() { threadName("OkHttp ${redactedUrl()}") { try { //獲取響應報文,並回調給Callback val response = getResponseWithInterceptorChain() responseCallback.onResponse(this@RealCall, response) } catch (e: IOException) { if (!signalledCallback) { responseCallback.onFailure(this@RealCall, e) } } catch (t: Throwable) { cancel() if (!signalledCallback) { responseCallback.onFailure(this@RealCall, canceledException) } } }
沒錯,這裏就是請求接口的地方了,經過getResponseWithInterceptorChain
方法獲取響應報文response
,而後經過Callback的onResponse
方法回調,或者是有異常就經過onFailure
方法回調。
那同步方法是否是就沒用到線程池呢?去找找execute
方法:
override fun execute(): Response { //... return getResponseWithInterceptorChain() }
果真,經過execute
方法就直接返回了getResponseWithInterceptorChain
,也就是響應報文。
到這裏,okhttp
的大致流程就結束了,這部分的流程大概就是:
設置請求報文 -> 配置客戶端參數 -> 根據同步或異步判斷是否用子線程 -> 發起請求並獲取響應報文 -> 經過Callback接口回調結果
剩下的內容就所有在getResponseWithInterceptorChain
方法中,這也就是okhttp的核心。
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) { interceptors += client.networkInterceptors } interceptors += CallServerInterceptor(forWebSocket) val chain = RealInterceptorChain( interceptors = interceptors //... ) val response = chain.proceed(originalRequest) }
代碼不是很複雜,就是 加加加 攔截器,而後組裝成一個chain類,調用proceed方法,獲得響應報文response。
override fun proceed(request: Request): Response { //找到下一個攔截器 val next = copy(index = index + 1, request = request) val interceptor = interceptors[index] val response = interceptor.intercept(next) return response }
簡化了下代碼,主要邏輯就是獲取下一個攔截器(index+1),而後調用攔截器的intercept
方法。
而後在攔截器裏面的代碼統一都是這種格式:
override fun intercept(chain: Interceptor.Chain): Response { //作事情A response = realChain.proceed(request) //作事情B }
結合兩段代碼,會造成一條鏈,這條鏈組織了全部鏈接器的工做。相似這樣:
攔截器1作事情A -> 攔截器2作事情A -> 攔截器3作事情A -> 攔截器3作事情B -> 攔截器2作事情B -> 攔截器1作事情B
應該是好理解的吧,經過proceed
方法把每一個攔截器鏈接起來了。
而最後一個攔截器ConnectInterceptor就是分割事情A和事情B,其做用就是進行真正的與服務器的通訊,向服務器發送數據,解析讀取的響應數據。
因此事情A和事情B是什麼意思呢?其實就表明了通訊以前
的事情和通訊以後
的事情。
再來個動畫:
這種思想是否是有點像..遞歸?沒錯,就是遞歸,先遞進執行事情A,再回歸作事情B。
而這種遞歸循環,其實也就是用到了設計模式中的 責任鏈模式
。
責任鏈模式(Chain of Responsibility Pattern)爲請求建立了一個接收者對象的鏈。這種模式給予請求的類型,對請求的發送者和接收者進行解耦。
簡單的說,就是讓每一個對象都能有機會處理這個請求,而後各自完成本身的事情,一直到事件被處理。Android中的事件分發機制
也是用到了這種設計模式。
接下來就是了解每一個攔截器到底作了什麼事,就能夠了解到okhttp
的整個流程了,這就是下期的內容了。
先預告一波:
addInterceptor(Interceptor)
,這是由開發者設置的,會按照開發者的要求,在全部的攔截器處理以前進行最先的攔截處理,好比一些公共參數,Header均可以在這裏添加。RetryAndFollowUpInterceptor
,這裏會對鏈接作一些初始化工做,以及請求失敗的重試工做,重定向的後續請求工做。BridgeInterceptor
,這裏會爲用戶構建一個可以進行網絡訪問的請求,同時後續工做將網絡請求回來的響應Response轉化爲用戶可用的Response,好比添加文件類型,content-length計算添加,gzip解包。CacheInterceptor
,這裏主要是處理cache相關處理,會根據OkHttpClient對象的配置以及緩存策略對請求值進行緩存,並且若是本地有了可⽤的Cache,就能夠在沒有網絡交互的狀況下就返回緩存結果。ConnectInterceptor
,這裏主要就是負責創建鏈接了,會創建TCP鏈接或者TLS鏈接,以及負責編碼解碼的HttpCodec。networkInterceptors
,這裏也是開發者本身設置的,因此本質上和第一個攔截器差很少,可是因爲位置不一樣,用處也不一樣。這個位置添加的攔截器能夠看到請求和響應的數據了,因此能夠作一些網絡調試。CallServerInterceptor
,這裏就是進行網絡數據的請求和響應了,也就是實際的網絡I/O操做,經過socket讀寫數據。讀完okhttp的源碼,感受就一個字:舒服
。
一份好的代碼應該就是這樣,各模塊之間經過各類設計模式進行解耦,閱讀者能夠每一個模塊分別去去閱讀了解,而不是各個模塊纏綿在一塊兒,雜亂無章。
最後再總結下okhttp
中涉及到的設計模式:
外觀模式
。經過okHttpClient這個外觀去實現內部各類功能。建造者模式
。構建不一樣的Request對象。工廠模式
。經過OkHttpClient生產出產品RealCall。享元模式
。經過線程池、鏈接池共享對象。責任鏈模式
。將不一樣功能的攔截器造成一個鏈。其實仍是有一些設計模式沒說到的,好比
觀察者模式
。迭代器模式
。之後遇到了再作補充吧。
https://www.runoob.com/design-pattern/design-pattern-tutorial.html
https://www.jianshu.com/p/ae2fe5481994
https://juejin.cn/post/6895369745445748749
感謝你們的閱讀,有一塊兒學習的小夥伴能夠關注下個人公衆號——碼上積木❤️❤️
每日一個知識點,聚沙成塔,創建知識體系架構。
這裏有一羣很好的Android小夥伴,歡迎你們加入~