OkHttp框架設計<一>---http家族史、OkHttp源碼分析、攔截器原理


關於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()方法的參數,那接下來要干嘛想都不用想嘛:

關於攔截器鏈發起請求的流程先不管,之后再分析,先來對上面整個同步和異步的請求流程做個總結:

高清大圖如:https://files.cnblogs.com/files/webor2006/okhttp%E4%B8%BB%E6%B5%81%E7%A8%8B%E6%97%B6%E5%BA%8F%E5%9B%BE.jpg.zip

攔截器機制剖析:

對於上面的流程分析中對於攔截器這塊請求細節一帶而過了,接下來則再來挼一挼它的整個流程:

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底層的連接是通過啥方式來實現的,這塊下次繼續。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM