OKHttp看這一篇就夠了!

寫在前面

本文按照一次網絡請求的代碼實例的順序來進行源碼分析,如果有錯誤的地方麻煩各位大佬指正~


目錄


實例

    /**
     * 同步請求
     */
    public void synRequest() {
        OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
        Request request = new Request.Builder()
                .url("http://www.baidu.com")
                .get()
                .build();

        Call call = client.newCall(request);
        try {
            Response response = call.execute();
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 異步請求
     */
    public void asyncRequest() {
        OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
        Request request = new Request.Builder()
                .url("http://www.baidu.com")
                .get()
                .build();

        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println(e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println(response.body().string());
            }
        });
    }

    public static void main(String[] args) {
        OkHttpTest test = new OkHttpTest();
        test.synRequest();
        test.asyncRequest();
    }

OkHttpClient

Call的工廠類,用於發送HTTP請求和讀取其響應。:

OkHttpClients 應該共享

當創建一個OkHttpClient實例並將其用於所有HTTP調用時,OkHttp的性能最佳。這是因爲每個客戶端都擁有自己的連接池和線程池,重用連接和線程可減少延遲並節省內存;相反,爲每個請求創建客戶端都會浪費空閒池上的資源。

使用newBuilder()自定義客戶端

您可以使用{@link #newBuilder()}自定義共享的OkHttpClient實例。這將構建共享相同連接池,線程池和配置的客戶端。使用構建器方法爲特定目的配置派生的客戶機。

不需要手動關閉連接

保留的線程和連接如果保持空閒狀態,將自動釋放。但是,如果編寫的應用程序需要主動釋放未使用的資源,則可以這樣做:

  • 1.使用{@link ExecutorService#shutdown()}關閉調度程序的執行程序服務。
    這也將導致將來打給客戶的電話被拒絕。
    client.dispatcher().executorService().shutdown();

  • 2.使用{@link ConnectionPool#evictAll()}清除連接池。請注意,連接池的守護程序線程可能不會立即退出。client.connectionPool().evictAll();

  • 3.如果您的客戶端具有緩存,調用{@link Cache#close()}。請注意,針對關閉的緩存創建調用是錯誤的,這樣做會導致調用崩潰。
    client.cache().close();

OkHttp還使用守護程序線程進行HTTP/2連接。如果它們保持空閒狀態,它們將自動退出。

總的來說,OkHttpClient將我們配置的參數和線程調度部分封裝在一起進行管理,提供Call的創建方法,爲網絡請求提供必要的組件。


Request

在這裏插入圖片描述

Request類中保存了url、method、headers、body和tags,當創建RealCall時作爲參數傳遞這些數據。


Call & RealCall

首先,我們先來看一下代碼上的流程:

在這裏插入圖片描述

當你看到後面覺得有些亂時,返回來看一下這張圖,就會感覺很舒服了~

接下來我們看這句:

  Call call = client.newCall(request);
  

  # OKHttpClient
  /**
   * 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
  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;
  }

可以看到,我們創建的是一個RealCall的實例對象,而RealCall的結構如下:

在這裏插入圖片描述

同步和異步請求調用了RealCall的execute()、enqueue()方法:

  // Guarded by this.
  // 通過該變量來標記RealCall是否被執行過了
  private boolean executed;

  /**
   * 同步請求調用
   */
  @Override 
  public Response execute() throws IOException {
    // 每個Call只能被執行一次
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    
    try {
      // 將RealCall傳遞給dispatcher統一管理
      client.dispatcher().executed(this);
      
      // 進入攔截器鏈(各種操作及網絡請求)
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      e = timeoutExit(e);
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      // release
      client.dispatcher().finished(this);
    }
  }
  
  
  /**
   * 異步請求調用
   */
  @Override 
  public void enqueue(Callback responseCallback) {
    // 每個Call只能被執行一次
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    
    captureCallStackTrace();
    eventListener.callStart(this);
    
    // 將RealCall傳遞給dispatcher統一管理
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
  
  // dispatcher的具體調度後文再講;異步請求被執行時會調用下面的RealCall #execute()
  
  @Override 
  protected void execute() {
      boolean signalledCallback = false;
      timeout.enter();
      try {
        // 進入攔截器鏈(各種操作及網絡請求)
        Response response = getResponseWithInterceptorChain();
        
        // 1.responseCallback是發送異步請求時傳進來的CallBack
        // 2.因爲是異步請求,在返回結果時會先判斷用戶是否已經取消了這次請求
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        e = timeoutExit(e);
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        // release
        client.dispatcher().finished(this);
      }
    }

注意這裏的兩個execute方法不是同一個:
同步請求調用的是聲明爲public的方法,而異步的調用比較複雜一些:enqueue將Call實例保存在dispatcher中,通過dispatcher來進行統一調度;當dispatcher決定執行Call時,會通過executeService(線程池)來調用Call,即實現Call接口的execute方法。dispatcher的調度在下一節中詳細的講。

這裏我們看到了同步請求和異步請求都調用了dispatcher的方法,這個並不是真正的網絡請求階段,而是通過dispatcher對我們想要執行的請求進行管理,詳細的代碼流程在下文中會講到,這裏只是總結一下;而enqueue操作,通過dispatcher中的executeService調度後,切換到工作線程執行網絡請求,調用到RealCall #execute()方法,最後也調用到了RealCall #getResponseWithInterceptorChain()方法。這個方法通過添加多個攔截器,對網絡請求進行補全、選擇協議、編解碼、複用緩存/連接、網絡請求等操作,以及添加自定義攔截器,對請求和返回的結果進行加工,最後返回數據。這就是整體的流程,關於攔截器的詳細講解也在下文中。先附上getResponseWithInterceptorChain()的代碼:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    
    // 自定義攔截器
    interceptors.addAll(client.interceptors());
    
    // 從故障中恢復,並根據需要進行重定向
    interceptors.add(retryAndFollowUpInterceptor);
    
    // 構建網絡請求、根據網絡響應構建response
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    
    // 提供緩存的請求、將response寫入緩存
    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);
  }

Dispatcher

首先我們看一下dispatcher的成員變量:

在這裏插入圖片描述

maxRequest & maxRequestPerHost

maxRequest和maxRequestPerHost用來控制當前可同時進行的網絡請求的數量。用這兩個變量去控制的原因在於,方便與對其進行修改和管理。因爲同步請求是直接進行請求的,而異步請求是通過線程池來進行請求的,我們來看一下線程池的獲取:

  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

這裏可以看到,線程池是直接創建了數量爲MAX_VALUE的,而後續的最大數量判斷都是使用了前面的兩個變量,這樣的好處是在對maxRequest進行修改時,excuteService本身並不需要重新實例化。

readyAsyncCalls & runningAsyncCalls & runningSyncCalls

這三個Deque的作用就是保存當前準備運行和正在運行的異步請求、正在運行的同步請求這三種請求的引用,用於統計數量、管理運行以及取消請求等一系列操作。

我們接着之前講到的,異步請求調用了enqueue之後,到執行Call接口的execute方法之間的過程:

  void enqueue(AsyncCall call) {
    synchronized (this) {
      // 加入ready隊列
      readyAsyncCalls.add(call);
    }
    
    // 執行異步請求
    promoteAndExecute();
  }
  
  /**
   * Promotes eligible calls from {@link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs
   * them on the executor service. Must not be called with synchronization because executing calls
   * can call into user code.
   * 將合格的請求從{@link #readyAsyncCalls}轉移到{@link #runningAsyncCalls},並在executor service(線程池)上執行。
   * 不能以同步方式調用,因爲執行調用可以調用用戶代碼。
   *
   * @return true if the dispatcher is currently running calls.
   * 如果dispatcher當前正在執行請求,返回true。
   */
  private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    
    synchronized (this) {
      // 遍歷readyAsyncCalls中的Call
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();

        // 判斷是否超過最大限制
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

        // 從readyAsyncCalls轉移到runningAsyncCalls
        i.remove();
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }

    // 執行這些請求
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }
  
  
    /**
     * Attempt to enqueue this async call on {@code executorService}. This will attempt to clean up
     * if the executor has been shut down by reporting the call as failed.
     * 嘗試在{@code executorService}上使此異步調用入隊。
     * 如果執行程序已通過將調用報告爲失敗而被關閉,則這將嘗試進行清理。
     */
    void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      
      try {
        // 調用Call接口的execute
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
        // 被拒絕執行後的清理操作
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        eventListener.callFailed(RealCall.this, ioException);
        responseCallback.onFailure(RealCall.this, ioException);
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // This call is no longer running!
        }
      }
    }

Interceptor

首先,我們先來看一下OKHttp添加的必須的攔截器;然後再介紹一下自定義攔截器。我們先來回顧一下:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    
    // 自定義攔截器
    interceptors.addAll(client.interceptors());
    
    // 從故障中恢復,並根據需要進行重定向
    interceptors.add(retryAndFollowUpInterceptor);
    
    // 構建網絡請求、根據網絡響應構建response
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    
    // 提供緩存的請求、將response寫入緩存
    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);
  }

那麼攔截器是怎麼進行操作的呢?如下面這張圖:每個攔截器按照其順序可以對Request進行處理,又按照反序對Response進行處理。而攔截器的核心函數就是public Response intercept(Chain chain)

在這裏插入圖片描述

RetryAndFollowUpInterceptor

重定向攔截器,負責從故障中恢復,並根據需要進行重定向。下文的代碼中部分需要大篇幅解釋的地方添加了註釋標誌:

  @Override 
  public Response intercept(Chain chain) throws IOException {
    // get request
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

    // 註釋1
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      // 如果取消,則釋放
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        // 進入鏈中的下一個攔截器,並catch RouteException
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        // 嘗試通過路由連接失敗,請求未被髮送
        // 註釋2
        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
          throw e.getFirstConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        // 嘗試與服務器通信失敗,該請求可能已發送
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Request followUp;
      try {
        // 找出響應收到{@code userResponse}而發出的HTTP請求
        // 並添加身份驗證標頭,遵循重定向或處理客戶端請求超時
        followUp = followUpRequest(response, streamAllocation.route());
      } catch (IOException e) {
        streamAllocation.release();
        throw e;
      }

      if (followUp == null) {
        streamAllocation.release();
        return response;
      }

      closeQuietly(response.body());

      // 判斷是否超過次數限制
      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }

      // 若重定向後不是同一連接,則重新創建StreamAllocation
      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(followUp.url()), call, eventListener, callStackTrace);
        this.streamAllocation = streamAllocation;
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;
      priorResponse = response;
    }
  }
  
  
  
  註釋1:
  此類協調三個實體之間的關係:
    Connections:與遠程服務器的物理套接字連接。這些建立起來可能很慢,因此必須能夠取消當前正在連接的連接。
    Streams:位於連接上的邏輯HTTP請求/響應對。每個連接都有自己的分配限制,該限制定義該連接可以承載的併發流數。 HTTP / 1.x連接一次只能傳送1個流,HTTP / 2通常會傳送多個流。
    Calls:流的邏輯順序,通常是初始請求及其後續請求。我們希望將單個呼叫的所有流都保留在同一連接上,以實現更好的行爲和局部性。
    
  此類的實例代表調用、使用一個或多個連接上的一個或多個流。此類具有用於釋放上述每個資源的API:
     {@ link #noNewStreams()}防止將來將連接用於新的流。在{@code Connection:close}標頭之後,或在連接可能不一致時使用此命令。
     {@ link #streamFinished streamFinished()}從此分配中釋放活動流。請注意,在給定時間可能只有一個流處於活動狀態,因此在使用{@link #newStream newStream()}創建後續流之前,必須調用{@link #streamFinished streamFinished()}。
     {@ link #release()}刪除呼叫對連接的保留。 請注意,如果仍然有流,這將不會立即釋放連接。 當呼叫完成但其響應主體尚未完全消耗時,就會發生這種情況。
     
  此類支持{@linkplain #cancel異步取消},目的是使影響範圍降到最小。 如果HTTP / 2流處於活動狀態,則取消操作將取消該流,但不會取消共享其連接的其他流。 但是,如果TLS握手仍在進行中,則取消操作可能會中斷整個連接。
  
  
  
  註釋2:
  /**
   * Report and attempt to recover from a failure to communicate with a server. Returns true if
   * {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
   * be recovered if the body is buffered or if the failure occurred before the request has been
   * sent.
   * 報告並嘗試從與服務器通信的故障中恢復。 
   * 如果{@code e}是可恢復的,則返回true;
   * 如果失敗是永久的,則返回false。
   * 僅在緩衝正文或在發送請求之前發生故障時,才能恢復帶有正文的請求。
   */
  private boolean recover(IOException e, StreamAllocation streamAllocation,
      boolean requestSendStarted, Request userRequest) {
      
    // 關閉並釋放連接
    streamAllocation.streamFailed(e);

    // The application layer has forbidden retries.
    if (!client.retryOnConnectionFailure()) return false;

    // We can't send the request body again.
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;

    // This exception is fatal.
    if (!isRecoverable(e, requestSendStarted)) return false;

    // No more routes to attempt.
    if (!streamAllocation.hasMoreRoutes()) return false;

    // For failure recovery, use the same route selector with a new connection.
    return true;
  }
  
  
BridgeInterceptor

構建網絡請求、根據網絡響應構建response

  @Override 
  public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    
    // 補全頭部信息
    
    // 補全Content-Type、Content-Length、Transfer-Encoding
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

    // 補全Host
    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    // 補全Connection
    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    // 如果增加了gzip壓縮的字段,還需要負責將返回的response解碼
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    // 補全Cookie
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

    // 分界線----上面是對Request的修改,下面是對Response的修改
    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    // 如果添加了gzip,需要進行解碼
    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }
CacheInterceptor

提供緩存的請求、將response寫入緩存

  @Override 
  public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    // 給定一個請求和緩存的響應,判斷是使用網絡,緩存還是同時使用兩者
    // 選擇緩存策略可能會向請求中添加條件(如GET的“ If-Modified-Since”)
    // 或向緩存的響應發出警告(如果緩存的數據可能已過時)
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    // 對{@code cacheStrategy}滿意的HTTP響應進行跟蹤
    // 註釋1
    if (cache != null) {
      cache.trackResponse(strategy);
    }

    // 沒有緩存策略,關閉候選緩存
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    // 沒有網絡策略和緩存策略,返回504
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If we don't need the network, we're done.
    // 不需要進行網絡通信,直接返回緩存
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      // ---分界線---
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    // 如果已經有一個緩存的response,那麼我們正在進行一個有條件的get請求
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        // 在合併headers之後、去掉Content-Encoding字段之前,更新緩存
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }
  
  
  
  註釋1:
    synchronized void trackResponse(CacheStrategy cacheStrategy) {
        ++this.requestCount;
        if (cacheStrategy.networkRequest != null) {
            ++this.networkCount;
        } else if (cacheStrategy.cacheResponse != null) {
            ++this.hitCount;
        }

    }
    
  
ConnectInterceptor

打開與目標服務器的連接,然後進入下一個攔截器

  @Override 
  public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    
    // 創建HttpCodec對象,用來編碼request、解碼response
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    
    // 用於進行實際的網絡IO傳輸
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
  

這裏的關鍵在於HttpCodec的創建,我們來看streamAllocation.newStream方法:

  public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    ···

    try {
      // co co da yo ~
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }
  
  
  /**
   * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
   * until a healthy connection is found.
   * 查找連接,如果連接狀況良好,則返回
   * 如果連接狀況不好,重複此過程,直到找到連接狀況良好的連接
   */
  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
      boolean doExtensiveHealthChecks) throws IOException {
    while (true) {
    
      // co co da yo ~
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);

      // If this is a brand new connection, we can skip the extensive health checks.
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }

      // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
      // isn't, take it out of the pool and start again.
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        noNewStreams();
        continue;
      }

      return candidate;
    }
  }

最後這裏就不貼代碼了。StreamAllocation中有一個ConnectionPool變量,負責管理Http連接。而這部分代碼的目的就是,在創建新的連接之前,需要先查看是否有可以複用的連接,如果有則複用,沒有或連接已經失效則重新創建連接。


CallServerInterceptor

進行網絡請求

  @Override 
  public Response intercept(Chain chain) throws IOException {
    ···
    
    // 寫入headers
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      ··· // 100-continue

      if (responseBuilder == null) {
        ···
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        // 寫入body信息
        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);
    }

    // build response
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    ··· // 100-continue

    realChain.eventListener()
            .responseHeadersEnd(realChain.call(), response);

    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.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    // prevents the connection from being used for new streams in the future
    // 禁止該連接被複用
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

自定義Interceptor

用戶可以自行定義攔截器,來進行對網絡請求的統一處理。這裏附上兩個攔截器:

PostParamsConvertInterceptor:將FormBody轉化爲json

LogInterceptor:打印請求的各種信息

使用方式也很簡單,在OkHttpClient創建時加入:

    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.addInterceptor(new PostParamsConvertInterceptor());
    if (Utils.isAppDebug()) {
        builder.addInterceptor(new LogInterceptor());
    }

寫在最後

如果文章中有錯誤的地方,希望各位大佬們批評指正~

If you like this article, it is written by Johnny Deng.
If not, I don’t know who wrote it.

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