OKHttp源碼解析

轉載自http://frodoking.github.io/2015/03/12/android-okhttp/

Android爲我們提供了兩種HTTP交互的方式:HttpURLConnection 和 Apache HTTP Client,雖然兩者都支持HTTPS,流的上傳和下載,配置超時,IPv6和連接池,已足夠滿足我們各種HTTP請求的需求。但更高效的使用HTTP可以讓您的應用運行更快、更節省流量。而OkHttp庫就是爲此而生。

OkHttp是一個高效的HTTP庫:

  • 支持 SPDY ,共享同一個Socket來處理同一個服務器的所有請求
  • 如果SPDY不可用,則通過連接池來減少請求延時
  • 無縫的支持GZIP來減少數據流量
  • 緩存響應數據來減少重複的網絡請求

會從很多常用的連接問題中自動恢復。如果您的服務器配置了多個IP地址,當第一個IP連接失敗的時候,OkHttp會自動嘗試下一個IP。OkHttp還處理了代理服務器問題和SSL握手失敗問題。

使用 OkHttp 無需重寫您程序中的網絡代碼。OkHttp實現了幾乎和java.net.HttpURLConnection一樣的API。如果您用了 Apache HttpClient,則OkHttp也提供了一個對應的okhttp-apache 模塊。

OKHttp源碼位置https://github.com/square/okhttp

##使用

簡單使用代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private final OkHttpClient client = new OkHttpClient();
 
public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();
 
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
 
    System.out.println("Server: " + response.header("Server"));
    System.out.println("Date: " + response.header("Date"));
    System.out.println("Vary: " + response.headers("Vary"));
}

在這裏使用不做詳細介紹,推薦一篇關於OKHttp的詳細使用教程,下面轉入源碼的分析。

##總體設計

上面是OKHttp總體設計圖,主要是通過Diapatcher不斷從RequestQueue中取出請求(Call),根據是否已緩存調用Cache或Network這兩類數據獲取接口之一,從內存緩存或是服務器取得請求的數據。該引擎有同步和異步請求,同步請求通過Call.execute()直接返回當前的Response,而異步請求會把當前的請求Call.enqueue添加(AsyncCall)到請求隊列中,並通過回調(Callback)的方式來獲取最後結果。

##請求流程圖
下面是關於OKHttp的請求流程圖

##詳細類關係圖
由於整個設計類圖比較大,所以本人將從核心入口client、cache、interceptor、網絡配置、連接池、平臺適配性…這些方面來逐一進行分析源代碼的設計。
下面是核心入口OkHttpClient的類設計圖

從OkHttpClient類的整體設計來看,它採用門面模式來。client知曉子模塊的所有配置以及提供需要的參數。client會將所有從客戶端發來的請求委派到相應的子系統去。
在該系統中,有多個子系統、類或者類的集合。例如上面的cache、連接以及連接池相關類的集合、網絡配置相關類集合等等。每個子系統都可以被客戶端直接調用,或者被門面角色調用。子系統並不知道門面的存在,對於子系統而言,門面僅僅是另外一個客戶端而已。同時,OkHttpClient可以看作是整個框架的上下文。
通過類圖,其實很明顯反應了該框架的幾大核心子系統;路由、連接協議、攔截器、代理、安全性認證、連接池以及網絡適配。從client大大降低了開發者使用難度。同時非常明瞭的展示了該框架在所有需要的配置以及獲取結果的方式。

在接下來的幾個Section中將會結合子模塊核心類的設計,從該框架的整體特性上來分析這些模塊是如何實現各自功能。以及各個模塊之間是如何相互配合來完成客戶端各種複雜請求。

##同步與異步的實現
在發起請求時,整個框架主要通過Call來封裝每一次的請求。同時Call持有OkHttpClient和一份HttpEngine。而每一次的同步或者異步請求都會有Dispatcher的參與,不同的是:

  • 同步
    Dispatcher會在同步執行任務隊列中記錄當前被執行過得任務Call,同時在當前線程中去執行Call的getResponseWithInterceptorChain()方法,直接獲取當前的返回數據Response;
  • 異步
    首先來說一下Dispatcher,Dispatcher內部實現了懶加載無邊界限制的線程池方式,同時該線程池採用了SynchronousQueue這種阻塞隊列。SynchronousQueue每個插入操作必須等待另一個線程的移除操作,同樣任何一個移除操作都等待另一個線程的插入操作。因此此隊列內部其 實沒有任何一個元素,或者說容量是0,嚴格說並不是一種容器。由於隊列沒有容量,因此不能調用peek操作,因爲只有移除元素時纔有元素。顯然這是一種快速傳遞元素的方式,也就是說在這種情況下元素總是以最快的方式從插入者(生產者)傳遞給移除者(消費者),這在多任務隊列中是最快處理任務的方式。對於高頻繁請求的場景,無疑是最適合的。
    異步執行是通過Call.enqueue(Callback responseCallback)來執行,在Dispatcher中添加一個封裝了Callback的Call的匿名內部類Runnable來執行當前的Call。這裏一定要注意的地方這個AsyncCall是Call的匿名內部類。AsyncCall的execute方法仍然會回調到Call的getResponseWithInterceptorChain方法來完成請求,同時將返回數據或者狀態通過Callback來完成。

接下來繼續講講Call的getResponseWithInterceptorChain()方法,這裏邊重點說一下攔截器鏈條的實現以及作用。

##攔截器有什麼作用
先來看看Interceptor本身的文檔解釋:觀察,修改以及可能短路的請求輸出和響應請求的回來。通常情況下攔截器用來添加,移除或者轉換請求或者回應的頭部信息。
攔截器接口中有intercept(Chain chain)方法,同時返回Response。所謂攔截器更像是AOP設計的一種實現。下面來看一個okhttp源碼中的一個引導例子來說明攔截器的作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public final class LoggingInterceptors {
  private static final Logger logger = Logger.getLogger(LoggingInterceptors.class.getName());
  private final OkHttpClient client = new OkHttpClient();

  public LoggingInterceptors() {
    client.networkInterceptors().add(new Interceptor() {
      @Override 
      public Response intercept(Chain chain) throws IOException {
        long t1 = System.nanoTime();
        Request request = chain.request();
        logger.info(String.format("Sending request %s on %s%n%s",
            request.url(), chain.connection(), request.headers()));
        Response response = chain.proceed(request);

        long t2 = System.nanoTime();
        logger.info(String.format("Received response for %s in %.1fms%n%s",
            request.url(), (t2 - t1) / 1e6d, response.headers()));
        return response;
      }
    });
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://publicobject.com/helloworld.txt")
        .build();

    Response response = client.newCall(request).execute();
    response.body().close();
  }

  public static void main(String... args) throws Exception {
    new LoggingInterceptors().run();
  }
}

返回信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
三月 19, 2015 2:11:29 下午 com.squareup.okhttp.recipes.LoggingInterceptors$1 intercept
信息: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA protocol=http/1.1}
Host: publicobject.com 
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: 

三月 19, 2015 2:11:30 下午 com.squareup.okhttp.recipes.LoggingInterceptors$1 intercept
信息: Received response for https://publicobject.com/helloworld.txt in 275.9ms
Server: nginx/1.4.6 (Ubuntu)
Date: Thu, 19 Mar 2015 06:08:50 GMT
Content-Type: text/plain
Content-Length: 1759
Last-Modified: Tue, 27 May 2014 02:35:47 GMT
Connection: keep-alive
ETag: "5383fa03-6df"
Accept-Ranges: bytes
OkHttp-Selected-Protocol: http/1.1
OkHttp-Sent-Millis: 1426745489953
OkHttp-Received-Millis: 1426745490198

從這裏的執行來看,攔截器主要是針對Request和Response的切面處理。
那再來看看源碼到底在什麼位置做的這個處理呢?爲了更加直觀的反應執行流程,本人截圖了一下執行堆棧

另外如果還有同學對Interceptor比較敢興趣的可以去源碼的simples模塊看看GzipRequestInterceptor.java針對HTTP request body的一個zip壓縮。

在這裏再多說一下關於Call這個類的作用,在Call中持有一個HttpEngine。每一個不同的Call都有自己獨立的HttpEngine。在HttpEngine中主要是各種鏈路和地址的選擇,還有一個Transport比較重要

##緩存策略
在OkHttpClient內部暴露了有Cache和InternalCache。而InternalCache不應該手動去創建,所以作爲開發使用者來說,一般用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public final class CacheResponse {
  private static final Logger logger = Logger.getLogger(LoggingInterceptors.class.getName());
  private final OkHttpClient client;

  public CacheResponse(File cacheDirectory) throws Exception {
    logger.info(String.format("Cache file path %s",cacheDirectory.getAbsoluteFile()));
    int cacheSize = 10 * 1024 * 1024; // 10 MiB
    Cache cache = new Cache(cacheDirectory, cacheSize);

    client = new OkHttpClient();
    client.setCache(cache);
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    Response response1 = client.newCall(request).execute();
    if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);

    String response1Body = response1.body().string();
    System.out.println("Response 1 response:          " + response1);
    System.out.println("Response 1 cache response:    " + response1.cacheResponse());
    System.out.println("Response 1 network response:  " + response1.networkResponse());

    Response response2 = client.newCall(request).execute();
    if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);

    String response2Body = response2.body().string();
    System.out.println("Response 2 response:          " + response2);
    System.out.println("Response 2 cache response:    " + response2.cacheResponse());
    System.out.println("Response 2 network response:  " + response2.networkResponse());

    System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
  }

  public static void main(String... args) throws Exception {
    new CacheResponse(new File("CacheResponse.tmp")).run();
  }
}

返回信息

1
2
3
4
5
6
7
8
9
10
信息: Cache file path D:\work\workspaces\workspaces_intellij\workspace_opensource\okhttp\CacheResponse.tmp
Response 1 response:          Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 cache response:    null
Response 1 network response:  Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 response:          Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 cache response:    Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 network response:  null
Response 2 equals Response 1? true

Process finished with exit code 0

上邊這一段代碼同樣來之於simple代碼CacheResponse.java,反饋回來的數據重點看一下緩存日誌。第一次是來至網絡數據,第二次來至緩存。
那在這一節重點說一下整個框架的緩存策略如何實現的。

在這裏繼續使用上一節中講到的運行堆棧圖。從Call.getResponse(Request request, boolean forWebSocket)執行Engine.sendRequest()和Engine.readResponse()來詳細說明一下。

sendRequest()
此方法是對可能的Response資源進行一個預判,如果需要就會開啓一個socket來獲取資源。如果請求存在那麼就會爲當前request添加請求頭部並且準備開始寫入request body。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public void sendRequest() throws IOException {
        if (cacheStrategy != null) {
            return; // Already sent.
        }
        if (transport != null) {
            throw new IllegalStateException();
        }

        //填充默認的請求頭部和事務。
        Request request = networkRequest(userRequest);

        //下面一行很重要,這個方法會去獲取client中的Cache。同時Cache在初始化的時候會去讀取緩存目錄中關於曾經請求過的所有信息。
        InternalCache responseCache = Internal.instance.internalCache(client);
        Response cacheCandidate = responseCache != null? responseCache.get(request): null;

        long now = System.currentTimeMillis();
        //緩存策略中的各種配置的封裝
        cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
        networkRequest = cacheStrategy.networkRequest;
        cacheResponse = cacheStrategy.cacheResponse;

        if (responseCache != null) {
            //記錄當前請求是來至網絡還是命中了緩存
            responseCache.trackResponse(cacheStrategy);
        }

        if (cacheCandidate != null && cacheResponse == null) {
            closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
        }

        if (networkRequest != null) {
            // Open a connection unless we inherited one from a redirect.
            if (connection == null) {
                //連接到服務器、重定向服務器或者通過一個代理Connect to the origin server either directly or via a proxy.
                connect();
            }
            //通過Connection創建一個SpdyTransport或者HttpTransport
            transport = Internal.instance.newTransport(connection, this);
            ...
        } else {
            ...
        }
    }

readResponse()
此方法發起刷新請求頭部和請求體,解析HTTP迴應頭部,並且如果HTTP迴應體存在的話就開始讀取當前迴應頭。在這裏有發起返回存入緩存系統,也有返回和緩存系統進行一個對比的過程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public void readResponse() throws IOException {
        ...
        Response networkResponse;

        if (forWebSocket) {
            ...
        } else if (!callerWritesRequestBody) {
            // 這裏主要是看當前的請求body,其實真正請求是在這裏發生的。
            // 在readNetworkResponse()方法中執行transport.finishRequest()
            // 這裏可以看一下該方法內部會調用到HttpConnection.flush()方法
            networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
        } else {
            ...
        }
		//對Response頭部事務存入事務管理中
        receiveHeaders(networkResponse.headers());

        // If we have a cache response too, then we're doing a conditional get.
        if (cacheResponse != null) {
			//檢查緩存是否可用,如果可用。那麼就用當前緩存的Response,關閉網絡連接,釋放連接。
            if (validate(cacheResponse, networkResponse)) {
                userResponse = cacheResponse.newBuilder()
                        .request(userRequest)
                        .priorResponse(stripBody(priorResponse))
                        .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                        .cacheResponse(stripBody(cacheResponse))
                        .networkResponse(stripBody(networkResponse))
                        .build();
                networkResponse.body().close();
                releaseConnection();

                // Update the cache after combining headers but before stripping the
                // Content-Encoding header (as performed by initContentStream()).
				// 更新緩存以及緩存命中情況
                InternalCache responseCache = Internal.instance.internalCache(client);
                responseCache.trackConditionalCacheHit();
                responseCache.update(cacheResponse, stripBody(userResponse));
				// unzip解壓縮response
                userResponse = unzip(userResponse);
                return;
            } else {
                closeQuietly(cacheResponse.body());
            }
        }

        userResponse = networkResponse.newBuilder()
                .request(userRequest)
                .priorResponse(stripBody(priorResponse))
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();

        //發起緩存的地方
        if (hasBody(userResponse)) {
            maybeCache();
            userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
        }
    }

##HTTP連接的實現方式(說說連接池)
外部網絡請求的入口都是通過Transport接口來完成。該類採用了橋接模式將HttpEngine和HttpConnection來連接起來。因爲HttpEngine只是一個邏輯處理器,同時它也充當了請求配置的提供引擎,而HttpConnection是對底層處理Connection的封裝。

OK現在重點轉移到HttpConnection(一個用於發送HTTP/1.1信息的socket連接)這裏。主要有如下的生命週期:

1、發送請求頭;
2、打開一個sink(io中有固定長度的或者塊結構chunked方式的)去寫入請求body;
3、寫入並且關閉sink;
4、讀取Response頭部;
5、打開一個source(對應到第2步的sink方式)去讀取Response的body;
6、讀取並關閉source;

下邊看一張關於連接執行的時序圖:

這張圖畫得比較簡單,詳細的過程以及連接池的使用下面大致說明一下:

1、連接池是暴露在client下的,它貫穿了Transport、HttpEngine、Connection、HttpConnection和SpdyConnection;在這裏目前默認討論HttpConnection;
2、ConnectionPool有兩個構建參數是maxIdleConnections(最大空閒連接數)和keepAliveDurationNs(存活時間),另外連接池默認的線程池採用了Single的模式(源碼解釋是:一個用於清理過期的多個連接的後臺線程,最多一個單線程去運行每一個連接池);
3、發起請求是在Connection.connect()這裏,實際執行是在HttpConnection.flush()這裏進行一個刷入。這裏重點應該關注一下sink和source,他們創建的默認方式都是依託於同一個socket:
this.source = Okio.buffer(Okio.source(socket));
this.sink = Okio.buffer(Okio.sink(socket));
如果再進一步看一下io的源碼就能看到:
Source source = source((InputStream)socket.getInputStream(), (Timeout)timeout);
Sink sink = sink((OutputStream)socket.getOutputStream(), (Timeout)timeout);
這下我想大家都應該明白這裏到底是真麼回事兒了吧?
相關的sink和source還有相應的細分,如果有興趣的朋友可以繼續深入看一下,這裏就不再深入了。不然真的說不完了。。。

其實連接池這裏還是有很多值得細看的地方,由於時間有限,到這裏已經花了很多時間搞這事兒了。。。

##重連機制
這裏重點說說連接鏈路的相關事情。說說自動重連到底是如何實現的。
照樣先來看看下面的這個自動重連機制的實現方式時序圖

同時回到Call.getResponse()方法說起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Response getResponse(Request request, boolean forWebSocket) throws IOException {
    ...
    while (true) { // 自動重連機制的循環處理
      if (canceled) {
        engine.releaseConnection();
        return null;
      }

      try {
        engine.sendRequest();
        engine.readResponse();
      } catch (IOException e) {
		//如果上一次連接異常,那麼當前連接進行一個恢復。
        HttpEngine retryEngine = engine.recover(e, null);
        if (retryEngine != null) {
          engine = retryEngine;
          continue;//如果恢復成功,那麼繼續重新請求
        }

        // Give up; recovery is not possible.如果不行,那麼就中斷了
        throw e;
      }

      Response response = engine.getResponse();
      Request followUp = engine.followUpRequest();
      ...
    }
  }

相信這一段代碼能讓同學們清晰的看到自動重連機制的實現方式,那麼我們來看看詳細的步驟:

1、HttpEngine.recover()的實現方式是通過檢測RouteSelector是否還有更多的routes可以嘗試連接,同時會去檢查是否可以恢復等等的一系列判斷。如果可以會爲重新連接重新創建一份新的HttpEngine,同時把相應的鏈路信息傳遞過去;
2、當恢復後的HttpEngine不爲空,那麼替換當前Call中的當前HttpEngine,執行while的continue,發起下一次的請求;
3、再重點強調一點HttpEngine.sendRequest()。這裏之前分析過會觸發connect()方法,在該方法中會通過RouteSelector.next()再去找當前適合的Route。多說一點,next()方法會傳遞到nextInetSocketAddress()方法,而此處一段重要的執行代碼就是network.resolveInetAddresses(socketHost)。這個地方最重要的是在Network這個接口中有一個對該接口的DEFAULT的實現域,而該方法通過工具類InetAddress.getAllByName(host)來完成對數組類的地址解析。
所以,多地址可以採用[“http://aaaaa","https://bbbbbb"]的方式來配置。

##Gzip的使用方式
在源碼引導RequestBodyCompression.java中我們可以看到gzip的使用身影。通過攔截器對Request 的body進行gzip的壓縮,來減少流量的傳輸。
Gzip實現的方式主要是通過GzipSink對普通sink的封裝壓縮。在這個地方就不再貼相關代碼的實現。有興趣同學對照源碼看一下就ok。

強大的Interceptor設計應該也算是這個框架的一個亮點。

##安全性
連接安全性主要是在HttpEngine.connect()方法。上一節油講到地址相關的選擇,在HttpEngine中有一個靜態方法createAddress(client, networkRequest),在這裏通過獲取到OkHttpClient中關於SSLSocketFactory、HostnameVerifier和CertificatePinner的配置信息。而這些信息大部分採用默認情況。這些信息都會在後面的重連中作爲對比參考項。

同時在Connection.upgradeToTls()方法中,有對SSLSocket、SSLSocketFactory的創建活動。這些創建都會被記錄到ConnectionSpec中,當發起ConnectionSpec.apply()會發起一些列的配置以及驗證。

建議有興趣的同學先了解java的SSLSocket相關的開發再來了解本框架中的安全性,會更能理解一些。

##平臺適應性
講了很多,終於來到了平臺適應性了。Platform是整個平臺適應的核心類。同時它封裝了針對不同平臺的三個平臺類Android和JdkWithJettyBootPlatform。
代碼實現在Platform.findPlatform中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
private static Platform findPlatform() {
    // Attempt to find Android 2.3+ APIs.
    try {
      try {
        Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl");
      } catch (ClassNotFoundException e) {
        // Older platform before being unbundled.
        Class.forName("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
      }

      OptionalMethod<Socket> setUseSessionTickets
          = new OptionalMethod<>(null, "setUseSessionTickets", boolean.class);
      OptionalMethod<Socket> setHostname
          = new OptionalMethod<>(null, "setHostname", String.class);
      Method trafficStatsTagSocket = null;
      Method trafficStatsUntagSocket = null;
      OptionalMethod<Socket> getAlpnSelectedProtocol = null;
      OptionalMethod<Socket> setAlpnProtocols = null;

      // Attempt to find Android 4.0+ APIs.
      try {
	  //流浪統計類
        Class<?> trafficStats = Class.forName("android.net.TrafficStats");
        trafficStatsTagSocket = trafficStats.getMethod("tagSocket", Socket.class);
        trafficStatsUntagSocket = trafficStats.getMethod("untagSocket", Socket.class);

        // Attempt to find Android 5.0+ APIs.
        try {
          Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0.
          getAlpnSelectedProtocol = new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
          setAlpnProtocols = new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);
        } catch (ClassNotFoundException ignored) {
        }
      } catch (ClassNotFoundException | NoSuchMethodException ignored) {
      }

      return new Android(setUseSessionTickets, setHostname, trafficStatsTagSocket,
          trafficStatsUntagSocket, getAlpnSelectedProtocol, setAlpnProtocols);
    } catch (ClassNotFoundException ignored) {
      // This isn't an Android runtime.
    }

    // Find Jetty's ALPN extension for OpenJDK.
    try {
      String negoClassName = "org.eclipse.jetty.alpn.ALPN";
      Class<?> negoClass = Class.forName(negoClassName);
      Class<?> providerClass = Class.forName(negoClassName + "$Provider");
      Class<?> clientProviderClass = Class.forName(negoClassName + "$ClientProvider");
      Class<?> serverProviderClass = Class.forName(negoClassName + "$ServerProvider");
      Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass);
      Method getMethod = negoClass.getMethod("get", SSLSocket.class);
      Method removeMethod = negoClass.getMethod("remove", SSLSocket.class);
      return new JdkWithJettyBootPlatform(
          putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass);
    } catch (ClassNotFoundException | NoSuchMethodException ignored) {
    }

    return new Platform();
  }

這裏採用了JAVA的反射原理調用到class的method。最後在各自的平臺調用下發起invoke來執行相應方法。詳情請參看繼承了Platform的Android類。
當然要做這兩種的平臺適應,必須要知道當前平臺在內存中相關的class地址以及相關方法。

##總結
1、從整體結構和類內部域中都可以看到OkHttpClient,有點類似與安卓的ApplicationContext。看起來更像一個單例的類,這樣使用好處是統一。但是如果你不是高手,建議別這麼用,原因很簡單:邏輯牽連太深,如果出現問題要去追蹤你會有深深地罪惡感的;
2、框架中的一些動態方法、靜態方法、匿名內部類以及Internal的這些代碼相當規整,每個不同類的不同功能能劃分在不同的地方。很值得開發者學習的地方;
3、從平臺的兼容性來講,也是很不錯的典範(如果你以後要從事API相關編碼,那更得好好注意對兼容性的處理);
4、由於時間不是很富裕,所以本人對細節的把握還是不夠,這方面還得多多努力;
5、對於初學網絡編程的同學來說,可能一開始學習都是從簡單的socket的發起然後獲取響應開始的。因爲沒有很好的場景能讓自己知道網絡編程到底有多麼的重要,當然估計也沒感受到網絡編程有多麼的難受。我想這是很多剛入行的同學們的一種內心痛苦之處;
6、不足的地方是沒有對SPDY的方式最詳細跟進剖析(手頭還有工作的事情,後面如果有時間再補起來吧)。

##結束語
很早前都打算花點時間好好來看一個值得學習的框架,今天終於算是弄得差不多了。我相信從框架的前期使用、到代碼的介入、再到源碼分模塊的剖析、最後到整理成文章。我想這都是一個很好的學習和成長的過程。

希望看到這篇文章的同學能做出評價,並且給出一些好的剖析點。

我也是一個普普通通的編碼人,只是內心多了一點點不“安分” ^.^。

##後續
最近看到一些網友建議把okhttp的連接池對Connection的重用維護機制以及HTTP和SPDY協議如何得到區分這兩部分內容做深入的分析
有需要的同學請移步:OKHttp源碼解析-ConnectionPool對Connection重用機制&Http/Https/SPDY協議選擇


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