關於Okhttp在之前有過一篇https://www.cnblogs.com/webor2006/p/10513950.html源碼的解讀,這里准備再對它進行溫故知新,並最終手寫整個OkHttp攔截鏈這塊的邏輯,鞏固再鞏固。
http家族史【了解】:
先來鞏固下基礎,畢境OkHttp是一個網絡框架。
網絡分成模型:
上面了解既可,關於網絡分成的一個原因之前在這篇有寫過:https://www.cnblogs.com/webor2006/p/10362197.html
OSI各層解釋:
各層對應的設備:
各層對應協議:
這個圖還是有點用,能夠清楚知道我們常見的一些協議是處於哪一層的。
TCP/IP 三次握手和四個揮手:
這個有時可能面試會問到,了解一下。
HTTP 1.1:
建立在TCP協議之上的”超文本傳輸協議”(HyperText Transfer Protocol),關於這塊的之前也已經研究過了:https://www.cnblogs.com/webor2006/p/10324182.html
HTTPS:
HTTP1.x在傳輸數據時,所有傳輸的內容都是明文,無法保證數據的安全性。
網景在1994年創建了HTTPS,HTTPS就是安全版的HTTP。
在通訊時多了一個SSL握手的過程:
具體握手過程大致過程如下:
具體整個Https的通信原理之前也有研究過:https://www.cnblogs.com/webor2006/p/10362197.html
當然對於Https的掌握肯定不是這么簡單的,待之后再找個時間深入研究一下,這塊在面試時也偶爾會被問到的。
OkHttp源碼再次梳理:
接下來則正式入進OkHttp的探究。
OkHttp版本選擇:
先上官網瞅一下如今最新的版本:
但是!!這次分析肯定不是基於最新版本,為啥?
不過從側面來看Kotlin這門語言真的是越來越重要了,所以搞Android的學好Koltin勢在必得,那學習選哪個版本呢?
當然是選3.x這個版本嘍,這里選擇3.14.2這個版本。
OkHttp簡單使用:
在正式源碼分析之前,還是回顧一下它的簡單使用,分為同步和異步,人人皆知的事,就不多說了,直接上代碼:
異步使用:
其簡單使用流程如下:
這塊簡單過一下,接下來則重點就是分析它的源碼了。
主流程源碼梳理:
初始化OkHttpClient:
先來分析同步的情況:
我們知道它里面用了經典的構建者模式,這里在默認構中實例化了Builder,看后瞅一下這個Builder構造里面做了啥?
最終再將此Builder中的數據再一一賦值給當前類的成員變量,如下:
其中這個Builder中定義了很多的參數,下面就不一一看了,以代碼注釋的方式貼出來供沒事回來復習用:
public static final class Builder { Dispatcher dispatcher; //調度器 /** * 代理類,默認有三種代理模式DIRECT(直連),HTTP(http代理),SOCKS(socks代理) */ @Nullable Proxy proxy; /** * 協議集合,協議類,用來表示使用的協議版本,比如`http/1.0,`http/1.1,`spdy/3.1,`h2等 */ List<Protocol> protocols; /** * 連接規范,用於配置Socket連接層。對於HTTPS,還能配置安全傳輸層協議(TLS)版本和密碼套件 */ List<ConnectionSpec> connectionSpecs; //攔截器,可以監聽、重寫和重試請求等 final List<Interceptor> interceptors = new ArrayList<>(); final List<Interceptor> networkInterceptors = new ArrayList<>(); EventListener.Factory eventListenerFactory; /** * 代理選擇類,默認不使用代理,即使用直連方式,當然,我們可以自定義配置, * 以指定URI使用某種代理,類似代理軟件的PAC功能 */ ProxySelector proxySelector; //Cookie的保存獲取 CookieJar cookieJar; /** * 緩存類,內部使用了DiskLruCache來進行管理緩存,匹配緩存的機制不僅僅是根據url, * 而且會根據請求方法和請求頭來驗證是否可以響應緩存。此外,僅支持GET請求的緩存 */ @Nullable Cache cache; //內置緩存 @Nullable InternalCache internalCache; //Socket的抽象創建工廠,通過createSocket來創建Socket SocketFactory socketFactory; /** * 安全套接層工廠,HTTPS相關,用於創建SSLSocket。一般配置HTTPS證書信任問題都需要從這里着手。 * 對於不受信任的證書一般會提示 * javax.net.ssl.SSLHandshakeException異常。 */ @Nullable SSLSocketFactory sslSocketFactory; /** * 證書鏈清潔器,HTTPS相關,用於從[Java]的TLS API構建的原始數組中統計有效的證書鏈, * 然后清除跟TLS握手不相關的證書,提取可信任的證書以便可以受益於證書鎖機制。 */ @Nullable CertificateChainCleaner certificateChainCleaner; /** * 主機名驗證器,與HTTPS中的SSL相關,當握手時如果URL的主機名 * 不是可識別的主機,就會要求進行主機名驗證 */ HostnameVerifier hostnameVerifier; /** * 證書鎖,HTTPS相關,用於約束哪些證書可以被信任,可以防止一些已知或未知 * 的中間證書機構帶來的攻擊行為。如果所有證書都不被信任將拋出SSLPeerUnverifiedException異常。 */ CertificatePinner certificatePinner; /** * 身份認證器,當連接提示未授權時,可以通過重新設置請求頭來響應一個 * 新的Request。狀態碼401表示遠程服務器請求授權,407表示代理服務器請求授權。 * 該認證器在需要時會被RetryAndFollowUpInterceptor觸發。 */ Authenticator proxyAuthenticator; Authenticator authenticator; /** * 連接池 * * 我們通常將一個客戶端和服務端和連接抽象為一個 connection, * 而每一個 connection 都會被存放在 connectionPool 中,由它進行統一的管理, * 例如有一個相同的 http 請求產生時,connection 就可以得到復用 */ ConnectionPool connectionPool; //域名解析系統 Dns dns; //是否遵循SSL重定向 boolean followSslRedirects; //是否重定向 boolean followRedirects; //失敗是否重新連接 boolean retryOnConnectionFailure; //回調超時 int callTimeout; //連接超時 int connectTimeout; //讀取超時 int readTimeout; //寫入超時 int writeTimeout; //與WebSocket有關,為了保持長連接,我們必須間隔一段時間發送一個ping指令進行保活; int pingInterval; public Builder() { dispatcher = new Dispatcher(); protocols = DEFAULT_PROTOCOLS; connectionSpecs = DEFAULT_CONNECTION_SPECS; eventListenerFactory = EventListener.factory(EventListener.NONE); /** * 代理選擇類,默認不使用代理,即使用直連方式,當然,我們可以自定義配置,以指定URI使用某種代理,類似代理軟件的PAC功能 */ proxySelector = ProxySelector.getDefault(); if (proxySelector == null) { proxySelector = new NullProxySelector(); } cookieJar = CookieJar.NO_COOKIES; socketFactory = SocketFactory.getDefault(); hostnameVerifier = OkHostnameVerifier.INSTANCE; certificatePinner = CertificatePinner.DEFAULT; proxyAuthenticator = Authenticator.NONE; authenticator = Authenticator.NONE; connectionPool = new ConnectionPool(); dns = Dns.SYSTEM; followSslRedirects = true; followRedirects = true; retryOnConnectionFailure = true; callTimeout = 0; connectTimeout = 10_000; readTimeout = 10_000; writeTimeout = 10_000; pingInterval = 0; } Builder(OkHttpClient okHttpClient) { this.dispatcher = okHttpClient.dispatcher; this.proxy = okHttpClient.proxy; this.protocols = okHttpClient.protocols; ..... }
}
初始化Request:
又是經典的建造者模式,大致瞅一下:
創建Call:
發起同步請求:
其實分發器的作用是用來管理請求的,瞅一下它里面定義的成員變量就曉得了:
而在請求前與請求后執行其實就是改變里面的狀態,如下:
接下來到最最核心的東東了,也是OkHttp框架的設計之魂,也是最終要手寫來實現的功能,閃亮讓它登場:
關於攔截器的細節下一次再來分析,這里以全局的流程為重,先忽略細節,現在只要知道經過這個攔截器鏈之后reponse就返回了,整個請求就結了。
發起異步請求:
基於上同步差不多,這里只分析跟同步不一樣的,當然就是發起請求這塊嘍:
此時分發器又出現了,然后傳了一個AsyncCall對像,一看不是一個實現了Runnable的類:
也就是最終會執行:
接下來線程的發起肯定是在分發器中的enqueue()方法中:
然后這個線程池作為AsyncCall.executeOn()方法的參數,那接下來要干嘛想都不用想嘛:
關於攔截器鏈發起請求的流程先不管,之后再分析,先來對上面整個同步和異步的請求流程做個總結:
攔截器機制剖析:
對於上面的流程分析中對於攔截器這塊請求細節一帶而過了,接下來則再來挼一挼它的整個流程:
Response getResponseWithInterceptorChain() throws IOException { List<Interceptor> interceptors = new ArrayList(); //用戶添加的全局攔截器 interceptors.addAll(this.client.interceptors()); //錯誤、重定向攔截器 interceptors.add(new RetryAndFollowUpInterceptor(this.client)); //橋接攔截器,橋接應用層與網絡層,添加必要的頭 interceptors.add(new BridgeInterceptor(this.client.cookieJar())); //緩存處理,Last-Modified、ETag、DiskLruCache等 interceptors.add(new CacheInterceptor(this.client.internalCache())); //連接攔截器 interceptors.add(new ConnectInterceptor(this.client)); if (!this.forWebSocket) { //通過okHttpClient.Builder#addNetworkInterceptor()傳進來的攔截器只對非網頁的請求生效 interceptors.addAll(this.client.networkInterceptors()); } //真正訪問服務器的攔截器 interceptors.add(new CallServerInterceptor(this.forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); boolean calledNoMoreExchanges = false; try { Response response = chain.proceed(originalRequest); if (transmitter.isCanceled()) { closeQuietly(response); throw new IOException("Canceled"); } return response; } catch (IOException e) { calledNoMoreExchanges = true; throw transmitter.noMoreExchanges(e); } finally { if (!calledNoMoreExchanges) { transmitter.noMoreExchanges(null); } } }
上面則是整個攔截器鏈方法的代碼,不是很多,但是理解起來不是很容易,有多個攔截器組成,下面分析一下:
攔截器【由於以前都詳細分析過了,這里先暫且過一下,重點是分析整個攔截器鏈的流程,為手寫做准備】:
用戶自定義應用攔截器:
而它則是在我們生成OkHttpClient時添加的,實際是用得最多的,拿它做日志打印,頭信息處理等等,使用如下:
它有啥用?看一下它的請求時機就知道了,它是在我們發起請求之前的一個自定義攔截器,所以一般可以搞一些請求前的數據處理。
RetryAndFollowUpInterceptor:錯誤、重定向攔截器
關於它這里就不細看了,之前已經分析過:https://www.cnblogs.com/webor2006/p/10513950.html
BridgeInterceptor:橋接攔截器,橋接應用層與網絡層,添加必要的頭
它也略過了,其中關於gzip的處理就是在這個攔截器中處理的,面試時有可能會問到,如下:
CacheInterceptor:緩存處理,Last-Modified、ETag、DiskLruCache等
貼一個關鍵請求頭的代碼:
也就是根據緩存相關的請求頭來做一些緩存處理,細節也略過。
ConnectInterceptor:連接攔截器
關於它的具體下次再來細分,總之這一步驟會和服務端建立socket通信,也就是其實OkHttp底層是通過Socket來實現的。
用戶自定義的網絡攔截器:
CallServerInterceptor:真正訪問服務器的攔截器
這里就真正的會發起跟服務器的具體通信,最終返回Resonse了,這里也不多說了。
攔截器鏈原理:
此時有個細節需要注意,傳了一個index=0,很顯然會先取第一個攔截器進行處理,那么下面來看一下整個鏈式的調用過程,直接來看一下它的procced方法:
看具體子類:
這里又創建了一個攔截器鏈對象,但是跟之前的不同的是:
而包裝了之后,接下來則會真正取出index的攔截器,然后再執行這個攔截器的intercept方法了,然后將新包裝的攔截器鏈又傳給這個在處理的攔截器的方法了:
此時調用會轉到第一個攔截器了:
此時又回到了攔截器鏈了,同樣的先包裝index+1=2的新攔截器鏈,然后取出當前index=1的鏈接器進行調用:
接着就取出第二個攔截器開始處理了:
然后又用同樣的套路:
其它鏈接的流程也類似就不一一分析了,直到最后一個攔截器執行:
在最后一個攔截器中可以發現,並沒有責任鏈procced的代碼了,而是處理完之后就返回response了,很簡單,因為整個請求鏈條執行完了,當然不需要再往下鏈了,此時就得往上一層層返,最終整個攔截器鏈的response就返回了。關於整個鏈式的過程之后會手動完整的來敲一遍的,目前了解整個的鏈式調用的關系既可,等手動自己實現一遍之后,到那時對於OkHttp的攔截器鏈這塊的東東就徹底的給掌握了,另外這里對於各個具體的攔截器只是一帶而過了,因為不想重復再看了,之前對這塊也已經詳細研究過了,不過有一個非常核心的攔截器需要細看一下,那就是ConnectInterceptor,為啥?就是要看OkHttp底層的連接是通過啥方式來實現的,這塊下次繼續。