1. 程式人生 > >五分鐘讀懂原始碼——Square開源網路請求庫OkHttp的工作原理解析

五分鐘讀懂原始碼——Square開源網路請求庫OkHttp的工作原理解析

前言

說句廢話,作為一個工作幾年的程式設計師,在日常工作中,難免會用一些三方封裝庫,來方便我們的開發,但是不能僅僅會用就滿足,我們還了解它的工作原理。

正文

轉入正題,看到本文的朋友應該知道了OK給出的API的基本呼叫(本文不對基本使用做介紹,有需要自行看https://github.com/square/okhttp/wiki/Recipes),
那下面試著揭開OK的面紗,檢視內部是如何實現的。
準備工作: 首先去下載https://github.com/square/okhttp原始碼,然後maven編譯完成後匯入IDEA中。

在分析工作流程前,需要先了解幾個類:OkHttpClient,Call,Request,Response。

OkHttpClient: Call的工廠,可以傳送HTTP請求和讀取響應,推薦多個請求共享同一個client,每一個 client都持有自己的connection pool(連線池)和(執行緒池),reuse 連線和執行緒,減少了延遲和節省了記憶體的開支;相反,對每一個Request建立一個client浪費空閒池中的資源。

推薦建立singleton HTTP client的方式
使用:
public final OkHttpClient client = new OkHttpClient();
或者:
public final OkHttpClient client = new OkHttpClient.Builder()
   .addInterceptor(new
HttpLoggingInterceptor()) .cache(new Cache(cacheDir, cacheSize)) .build(); 執行緒和連線在空閒時會自動釋放。 關閉 dispatcher's executor service ,This will also cause future calls to the client to be rejected client.dispatcher().executorService().shutdown(): 主動釋放後,以後的Call 呼叫也會被拒絕。 Clear the connection pool with
client.connectionPool().evictAll() Note that the 清除連線池的操作:client.connectionPool().evictAll(),connection pool's daemon thread may not exit immediately. 連線池的守護執行緒可能不會立即退出。 關閉客戶端快取的方法 如果client 有cache,呼叫client.cache().close(),注:建立Calls 使用cache,但是設定了關閉cache會導致報錯? If your client has a cache, call {@link Cache#close close()}. Note that it is an error to create calls against a cache that is closed, and doing so will cause the call to crash. Call :A call is a request that has been prepared for execution. 準備被執行的請求,可以取消,一個call例項表示一個請求/響應對(流),只能執行一次。 Request:An HTTP request. Response:An HTTP response 這個類的例項不是不可變的,ResponseBody(響應體)是一個一次性的值,只被使用一次,然後被關閉。所有其他屬性都是不可變的。

接下來,就拿下面這個Get請求為例,看下Ok的內部工作流程吧。

package okhttp3.guide;

import java.io.IOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class GetExample {
  OkHttpClient client = new OkHttpClient();

  String run(String url) throws IOException {
    Request request = new Request.Builder()
        .url(url)
        .build();

    try (Response response = client.newCall(request).execute()) {
      return response.body().string();
    }
  }

  public static void main(String[] args) throws IOException {
    GetExample example = new GetExample();
    String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
    System.out.println(response);
  }
}

流程描述:初始化OkHttpClient 物件,接下來執行run()方法,裡面對Request物件進行初始化,然後client.newCall(request).execute()發起請求,返回結果Response。

第一步OkHttpClient初始化

首先對OkHttpClient初始化操作,發起Request前進行預設初始化設定,相當於準備工作。
下面是OkHttpClient 類中的部分屬性。

  final Dispatcher dispatcher;
  final @Nullable Proxy proxy;
  final List<Protocol> protocols;
  final List<ConnectionSpec> connectionSpecs;
  final List<Interceptor> interceptors;
  final List<Interceptor> networkInterceptors;
  final EventListener.Factory eventListenerFactory;
  final ProxySelector proxySelector;
  final CookieJar cookieJar;
  final @Nullable Cache cache;
  final @Nullable InternalCache internalCache;
  final SocketFactory socketFactory;
  final @Nullable SSLSocketFactory sslSocketFactory;
  final @Nullable CertificateChainCleaner certificateChainCleaner;
  final HostnameVerifier hostnameVerifier;
  final CertificatePinner certificatePinner;
  final Authenticator proxyAuthenticator;
  final Authenticator authenticator;
  final ConnectionPool connectionPool;
  final Dns dns;
  final boolean followSslRedirects;
  final boolean followRedirects;
  final boolean retryOnConnectionFailure;
  final int connectTimeout;
  final int readTimeout;
  final int writeTimeout;
  final int pingInterval;

第二步 構建Request

接下來構建Request,指定是設定Request的 url ,請求型別 such as get/post …
以及request headers or request body , tag 。

下面是Request類中定義的部分屬性。

/**
 * An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself
 * immutable.
 */
public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Object tag;
  private volatile CacheControl cacheControl; // Lazily initialized.

第三步發起Request返回Response

下面是發起請求的程式碼,我們拆開來看

try (Response response = client.newCall(request).execute()) {
      return response.body().string();
    }

client.newCall(request) 執行完後返回了Call 物件。

  /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

我們可以看出RealCall.newRealCall(),返回了Call物件,看下RealCall

final class RealCall implements Call {
    /**省略部分程式碼*/
  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

 @Override public Response execute() throws IOException {
   /**省略部分程式碼*/
    try {
      client.dispatcher().executed(this);
      //=============關鍵程式碼==============
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
    /**省略部分程式碼*/
  }

可以看出RealCall 實現了 Call介面,靜態方法newRealCall初始化一個RealCall物件,並且有一個execute()方法,這個方法返回值就是Response 物件,也就是說,我們呼叫client.newCall(request).execute()發起Request,然後返回 Response。

 Response response = getResponseWithInterceptorChain();

RealCall的execute()方法中有這樣一行程式碼,很顯然,是getResponseWithInterceptorChain()拿到了對應的Response 資訊,一起看下RealCall中的這個方法

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    return chain.proceed(originalRequest);
  }

getResponseWithInterceptorChain方法首先把所有的interceptors封裝到一個集合中,然後把這個集合作為引數,初始化RealInterceptorChain【RealInterceptorChain是攔截器鏈,承載所有的攔截器鏈,包含:所有的自定義攔截器,OK核心,所有的網路攔截器,及網路呼叫。】
最後執行proceed(),現在我們一步步靠近Ok請求的核心了,看下proceed方法

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    /**省略部分程式碼*/
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
     //=============關鍵程式碼==============
    Response response = interceptor.intercept(next);
    /**省略部分程式碼*/
    return response;
  }

可以看到 chain內部的proceed方法最終還是執行的interceptor.intercept()方法拿到了Response,那幾回過頭看下前面getResponseWithInterceptorChain()方法中都有那些interceptor

 //只看interceptor
  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //client我們自己設定的interceptor
    interceptors.addAll(client.interceptors());
    //retryAndFollowUpInterceptor:請求失敗時,重試和重定向攔截器。
    interceptors.add(retryAndFollowUpInterceptor);
    //從應用程式程式碼到網路程式碼的橋樑
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //快取攔截器 從cache中讀取response和寫response到 cache
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //開啟到目標伺服器的連線,並繼續執行下一個攔截器
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //攔截器鏈中最後一個interceptor,It makes a network call to the server
    interceptors.add(new CallServerInterceptor(forWebSocket));
    /**省略部分程式碼*/
  }

註釋很明顯,最後一個CallServerInterceptor攔截器的任務是向Remote Server發起Request。又前面我們知道 呼叫的都是Interceptor的 intercept()方法,那最終,我們跟蹤一個發起的Request到了 CallServerInterceptor的intercept方法中。

@Override public Response intercept(Chain chain) throws IOException {
    /**
     * * A concrete interceptor chain that carries the entire interceptor chain: all application
     * interceptors, the OkHttp core, all network interceptors, and finally the network caller.
     * 攔截器 鏈 ,ok的核心,攔截器是網咯的呼叫者
     */
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    /**
     * Encodes HTTP requests and decodes HTTP responses. 編碼http請求,解碼http響應
     */
    HttpCodec httpCodec = realChain.httpStream();
    /**
     * 協調 Connections 、Stream、 Calls 之間關係的類。
     * Connections: physical socket connections to remote servers  :物理Socket 連線遠端伺服器
     * Streams:HTTP request/response pairs  HTTP 請求/響應對。
     * Calls:a logical sequence of streams, typically an initial request and its follow up requests.
     *  一系列的流,通常是初始請求及後續請求,推薦每個call上面所有的流,使用相同的連結。
     * We prefer to keep all streams of a single call on the same connection for better behavior and locality.
     */
    StreamAllocation streamAllocation = realChain.streamAllocation();
    /**
     *  HTTP套接字和流  傳送和接收資料
     */
    RealConnection connection = (RealConnection) realChain.connection();
    /**
     * HTTP請求
     */
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();
    /**
     * 開始處理請求頭
     */
    realChain.eventListener().requestHeadersStart(realChain.call());
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    /**
     * 判斷請求方法 和請求body 不為空
     */
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.

      if (responseBuilder == null) {
        /**
         * Write the request body if the "Expect: 100-continue" expectation was met.
         * 如果“期望:100繼續”的期望得到滿足,請編寫請求體。
         */
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      } else if (!connection.isMultiplexed()) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
        // from being reused. Otherwise we're still obligated to transmit the request body to
        // leave the connection in a consistent state.
        streamAllocation.noNewStreams();
      }
    }
    httpCodec.finishRequest();

    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(false);
    }
    /**
     * 初始化response物件
     */
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();
    /**
     * 響應頭
     */
    realChain.eventListener()
        .responseHeadersEnd(realChain.call(), response);

    int code = response.code();

    if (forWebSocket && code == 101) {
      /**
       *Connection is upgrading, but we need to ensure interceptors see a non-null response body.
       連線正在升級,但是我們需要確保攔截器看到一個非空響應體
        */
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      /**
       * 最終執行到這裡將response物件,構建完成,然後返回
       * 構建response 的ResponseBody
       */
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();

    }
    //===========刪除部分程式碼========================

    return response;
  }

這個方法比較重要,所以要看仔細一點,由此,我們可以得到我們想要的結果了。

結論

一個Request的發起到接收Response 經歷的過程:
首先構建OkHttpClient 物件,接下來使用構建好的Request作為引數傳遞給OkHttpClient 的newCall() 生成RealCall 物件,然後執行call的execute方法,最終這個方法內部通過CallServerInterceptor返回了Response,Response 封裝了伺服器返回的資料。

大體流程就是這樣,更多細節與流程圖稍後補充。

歡迎愛學習的小夥伴加群一起進步:230274309 。
一起分享,一起進步!少划水,多晒乾貨!!歡迎大家!!!(進群潛水者勿加)