你必須學會的OkHttp,帶你快速上車

引子

OkHttp 知名第三方網絡框架SDK,使用簡單,性能優秀,但是內核並不簡單,此係列文章,專挑硬核知識點詳細講解. 何爲硬核,就是要想深入研究,你絕對繞不過去的知識點

正文大綱

  • OkHttp是什麼?
  • OkHttp怎麼用?
  • OkHttp源碼核心類之一:分發器詳解
  • OkHttp源碼核心類之一:攔截器簡述

一.正文

1.OkHttp是什麼

OkHttp是時下非常流行的網絡編程框架,由行業巨佬 Square公司開源,很多其他的流行框架比如 retrofit的底層也是okhttp,只不過使用了註解反射動態代理將其進行了封裝。流行版本爲:3.10.0,最新版本爲:4.0.1,只不過將實現語言從java改成了kotlin。

相對於其他網絡框架,有如下優點:

  • 支持 Spdy、 Http1.X、 Http2、 Quic以及 WebSocket
  • 連接池複用底層 TCP(Socket),減少請求延時
  • 無縫的支持 GZIP減少數據流量
  • 緩存響應數據減少重複的網絡請求
  • 請求失敗自動重試主機的其他 ip,自動重定向
2.OkHttp怎麼用

添加gradle依賴

dependencies {
   ....
    implementation ("com.squareup.okhttp3:okhttp:4.0.1")
}

Java調用(同步請求,異步請求)

public class MyRequest {
    /**
     * 異步請求
     */
    public void sendReqAsync() {
        OkHttpClient client = new OkHttpClient.Builder().build();
        Request request=newRequest.Builder().url("http://www.baidu.com").build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call,@NotNull IOException e) {
                Log.d("sendReqTag", "onFailure:"+e.getLocalizedMessage());
            }

            @Override
            public void onResponse(@NotNull Call call,@NotNull Response response) throws IOException {
                String s = new String().concat(response.code() +"\n")
                        .concat(response.message()+"\n")
                        .concat(response.body().string());
                Log.d("sendReqTag","onSuccess\n "+ s);
            }
        });
    }
    /**
     * 同步請求
     */
    public void sendReqSync() {
        OkHttpClient client = new OkHttpClient.Builder().build();
        Request request = new Request.Builder().url("http://www.baidu.com").build();
        Call call = client.newCall(request);
        try {
            Response response = call.execute();
            String s = new String().concat(response.code() + "\n")
                    .concat(response.message() + "\n")
                    .concat(response.body().string());
            Log.d("sendReqTag","onSuccess\n "+s);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

OkHttp的簡單使用方法大致使用如上,其中也有一些細節需要注意:

1、在應用層使用OkHttp,必然會涉及到4個重要元素:

  • OkHttpClient類(產生OkHttp客戶端實例)
  • Request類(請求封裝)
  • Call類(網絡任務封裝,並決定是要 同步執行還是 異步執行,注意,同步-
    請求 不可以放在 主線程中,但是異步請求可以 )
  • Response類(網絡任務執行之後的回調)

2.執行網絡請求必須在manifest中申請 INTERNET權限,不然會拋異常.

3、完整的一個請求執行出去,流程如下圖:
你必須學會的OkHttp,帶你快速上車

OkHttp源碼核心類之一:分發器詳解

上述,提到Call類,可以選擇性執行 同步或者異步請求,但是無論同步異步,都一定會經過一個門戶:"分發器" : 索引進源碼(okhttp v3.10.0):
你必須學會的OkHttp,帶你快速上車
你必須學會的OkHttp,帶你快速上車

雖然用戶不需要直接操作分發器,但是 分發器,作爲 OkHttp架構的一個門戶層,是所有請求的必經之路,其中的代碼還是有必要了解細節的。

3.同步請求

進入 分發器Dispatcher之後, 會執行 getResponseWithInterceptorChain()來執行這個 Call任務,得到一個 Response,其中的細節分爲兩步:

1、client.dispatcher().executed(this);,進入源碼可以看到 僅僅是執行了runningSyncCalls.add(call);,將call對象加入到了一個雙端隊列 Deque<RealCall>runningSyncCalls 中。
2、 getResponseWithInterceptorChain() 是執行網絡請求的核心內容,涉及到攔截器,在這一節上暫時不詳述。

同步請求的執行步驟十分簡單,將任務加入到 runningSyncCalls列表,並且直接執行核心方法,同步阻塞拿到response。

4.異步請求

異步請求進入分發器之後,
你必須學會的OkHttp,帶你快速上車
可能會被加入到 Deque<AsyncCall>runningAsyncCalls 這麼一個雙端隊列中,然後 executorService().execute(call);實際上是用了線程池來執行了這個異步任務。但是,請注意(還是剛纔的enqueue方法代碼)這裏有一個判斷條件 if分支 :
你必須學會的OkHttp,帶你快速上車
這個條件是否滿足,將會直接決定是直接執行這個任務,還是將任務加入到 readyAsyncCalls 雙端隊列.

那麼設置這個條件的目的是什麼呢?從變量命名來看: runningAsyncCalls 執行中的異步任務 runningCallsForHost 同一個域名正在執行的任務數 readyAsyncCalls 預備執行的任務隊列(尚未執行)

當正在執行的任務數小於最大值(默認爲64)並且,同一個域名正在請求的任務數小於最大值(默認5)時,纔會立即執行,否則,這個任務會被加入到 readyAsyncCalls中等待安排。

那麼問題來了,readyAsyncCalls中的任務什麼時候會被執行?追蹤代碼:追蹤 readyAsyncCalls 的使用代碼,找到 遍歷這個隊列的地方:
你必須學會的OkHttp,帶你快速上車
繼續追蹤,找到了這個 finish方法:
你必須學會的OkHttp,帶你快速上車
繼續追蹤finish在哪裏調用的,找到兩處:
你必須學會的OkHttp,帶你快速上車
你必須學會的OkHttp,帶你快速上車
所以,得出結論:在一個任務(無論同步還是異步)結束之後,分發器中的異步任務,存在兩個隊列,一個 running隊列,一個 ready隊列,當 running隊列的size小於最大值,並且同一個域名正在執行的任務數小於最大值時,可以直接加入到running隊列,立即執行。如果不滿足這條件,這個異步任務就會被加入到 ready隊列.

在任意一個任務( 無論同步或是異步任務)執行完畢( 無論成敗)之後,就會遍歷 ready隊列,每次從 ready隊列中取出一個任務,判斷同時執行的異步任務數是否達到上限,並且同一主機的訪問數是否達到上限,如果都滿足,就加入到running隊列,並且立即執行,不滿足,就停止遍歷。周而復始,直到所有的異步任務都執行完。

文字不夠形象,畫個圖表示。
你必須學會的OkHttp,帶你快速上車

關於okhttp的分發器Dispatcher用到的線程池

同步請求,沒有用到線程池。
你必須學會的OkHttp,帶你快速上車

但是異步請求的代碼中,有這麼一句。
你必須學會的OkHttp,帶你快速上車

我們知道,爲什麼這裏會用到線程池呢?
1.觀察 同步或者異步的call的實例
你必須學會的OkHttp,帶你快速上車
那麼這個 Call是什麼?它是一個接口,它的唯一實現類是 RealCall,
你必須學會的OkHttp,帶你快速上車
RealCall中,異步請求的執行方法,enqueue() 其實是交給了 分發器一個AsyncCall對象,它繼承自 NamedRunnable可命名的 Runnable任務。所以,這裏可以用 線程池 ExecutorService來執行這個Runnable.

進一步觀察這個線程池的細節:
你必須學會的OkHttp,帶你快速上車
它是一個核心線程數爲0的線程池,並且使用了一個無容量的阻塞隊列作爲參數。其實也不不必自己去創建線程池,而可以直接使用 Executors.newCachedThreadPool(); 來創建,效果一樣。線程池,系統提供了有多種默認實現
你必須學會的OkHttp,帶你快速上車

爲什麼okhttp偏偏選擇了這一種?

爲了實現最大併發量。

詳解如下:既然這裏提到了線程池,那麼就把線程池的基本機制整理一下:
你必須學會的OkHttp,帶你快速上車
線程池的構造函數中,有一個阻塞隊列參數。
你必須學會的OkHttp,帶你快速上車
它有3個實現類:ArrayBlockingDeque/ LinkedBlockingDeque / SynchronousQueue 是我們線程池經常用的。前面2個都是有容量的,而第三個是無容量的,加入進去,一定會失敗。而參照上面線程池的工作流程圖,如果加入失敗,就會嘗試去非核心線程執行任務。這樣,便保證了每一個提交進來的異步任務,都會立即嘗試去執行,而不是塞入等待隊列中等待空閒線程,從而確保了 異步任務的併發。

OkHttp源碼核心類之一:攔截器簡述

上面講解分發器的時候,提到了 RealCall類的 getResponseWithInterceptorChain()方法。它是一個網絡請求執行的真正核心方法。進入方法:
你必須學會的OkHttp,帶你快速上車

  • 新建一個攔截器List,並且放入各種攔截器對象
  • 將攔截器list,交給RealInterceptorChain,進行責任鏈模式的調用,最終得出Response.

首先解釋一下 責任鏈模式,它是21種基本設計模式中,行爲模式中一種。下面的案例可以很好地解釋它:
你必須學會的OkHttp,帶你快速上車
當一個國企要採購一批設備的時候,按照上圖整個任務流程中,存在5個對象,都能對採購流程造成影響,採購任務開始的時候,是從上到下依次對採購流程負責。而總經理,他纔不關心下面的人怎麼操作,他只關心最後的結果。正如此案例中所述,okhttp的責任鏈模式,使用者也不需要關心這個請求到底經歷了哪些過程,他只知道,我給了request,你就要給我response,而過程中,發生作用的各類攔截器,無需使用者知道,這樣就達成了 面向對象程序開發中的 最少知道原則。

而,這些攔截器,恰恰是okhttp的核心內容,下篇文章將會詳細講解。

三.結語

本文是 okhttp的開篇,如果要詳細解讀 okhttp的每個細節,每一篇文章將會顯得 非常冗長而且乏味,所以我選了重要節點着重分析。就像攻城略地打天下,先 佔領據點,再 企圖擴張,一步一個腳印,穩紮穩打,才能長遠發展

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章