源碼解析-OkHttp

爲什麼要了解OkHttp?
市面上很多Android開發都在使用OkHttp框架,而且像現在最流行的rxjava+retrofit結合,retrofit中默認使用的網絡請求方式也是OkHttp。Okhttp有個強大的機制-攔截器。它可以實現網絡監聽、請求及相應重寫、請求失敗重試等功能。

一、OkHttp的簡單使用

在查看源碼前我們來回顧下OkHttp的使用

    //第一步
    private final OkHttpClient client = new OkHttpClient();
    public void okhttpAsyGet() throws IOException {
        //第二步
        Request request = new Request.Builder()
                .url("http://ip:port/xxxx")
                .build();
        //第三步---同步請求
        Response response = client.newCall(request).execute();
        
        //第三 步---異步請求
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
            }
        });
    }

1.創建一個OkHttpClient對象(創建一個作爲全局變量)

2.創建一個request對象,通過內部類Builder調用生成Request對象

3.創建一個Call對象,調用execute(同步請求使用)或者enqueue(異步請求使用)

二、OkHttp源碼

前2個步驟都是屬於創建的過程。我們就直接從第三步開始看,先看同步請求execute。執行execute的是newCall返回的類,我們來看下newCall方法:

  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

newCall方法返回的是RealCall,那麼我們就查看下RealCall的

execute同步請求方法:

  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

看了下代碼很短。但是很精髓,我們一步步來看。

1)首先會進行一個同步判斷,看這個executed請求是否已經被執行。

2)然後會執行captureCallStackTrace方法,看名字我們應該知道是用來追蹤棧的信息。這裏不深究。

3)然後再執行dispatcher的executed方法。executed代碼:

  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

可以看到只是將RealCall放到同步請求的隊列中。

4)調用getResponseWithInterceptorChain方法。

這一步是很關鍵的一個方法,獲取Http網絡請求的返回結果進行返回,並且在這一步中執行了一系列的攔截操作。待會進一步說明。

5)請求執行完後無論成功與否都會調用dispatcher的finished方法,通知分發器任務已經結束了。

在同步請求的執行中,dispatcher沒有起到太多的作用,真正值得關注的是getResponseWithInterceptorChain方法。現在我們來看下這個RealCall的getResponseWithInterceptorChain:

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);
    return chain.proceed(originalRequest);
  }

一進來就看到很多Interceptor(攔截器)加進列表中,然後加進Interceptor的chain(鎖鏈)中,這個chain就是最終調用請求的類。我們也可以稱它爲 攔截器鏈,我們知道Okhttp的攔截器機制很強大,那麼我們來看下這些攔截器都起到什麼作用:

1)RetryAndFollowUpInterceptor:失敗重試重定向的攔截器,比如一個網絡請求失敗可以重新指向一個新的url進行請求。

2)BridgeInterceptor:將用戶的請求轉化爲到服務器的請求,將服務器返回的響應轉回給用戶。進行的是友好輸出的響應。可以理解爲橋接的作用。

3)CacheInterceptor:緩存的攔截器,負責直接讀取緩存或者更新緩存的攔截器。若緩存中有所需的請求響應,就不再執行下面的操作。

4)ConnectInterceptor:負責與服務器連接的攔截器。藉助前面RetryAndFollowUpInterceptor分配好的StreamAllocation對象,建立與服務器的連接並且選定所用的交互協議是HTTP1.1還是HTTP2。

5)RetryAndFollowUpInterceptor:負責處理IO。與服務器進行數據的交換。

 

下面我們來看下RetryAndFollowUpInterceptor的源碼作爲了解。

public final class CallServerInterceptor implements Interceptor {
  private final boolean forWebSocket;

  public CallServerInterceptor(boolean forWebSocket) {
    this.forWebSocket = forWebSocket;
  }

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

    long sentRequestMillis = System.currentTimeMillis();
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
    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 ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      } 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) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

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

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

    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;
  }
}

可以看出代碼也不多
1)首先是將http請求頭部發送給服務器,如果有body的話會再將boby發送給服務器,而後通過httpStream.finnishRequest()結束http請求的發送。

2)隨後便從連接中讀取服務的返回響應,構建Response。

3)判斷 Connection值是否爲close。是則CallServerInterceptor則關閉連接。

4)返回response。

這裏值得關注 的是HttpCodec 這個對象,這個對象是主要負責IO處理的,而這個對象的實體類內部就是使用okio。而okio封裝了Socket。也就是說其實這個HttpCodec實體類本質上還是對Socket的一層層封裝出來的類。當然封裝出來的效果就是讓我們更容易的使用。

另外說明下,Interceptor的設計是一種分層的思想,和tip/ip協議是有異曲同工之妙。每一層都只需要負責自己這層所該負責的任務,把複雜的任務拆分成一個個具體的獨立的任務。這樣對以後的擴展很有幫助。

 

下面我們來看下enqueue異步請求和execute同步請求有什麼不同:

RealCall的enqueue方法:

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

我們可以看到最後還是調用了diapatcher的enqueue方法,我們進去看下:

Diapatcher的enqueue方法:

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

看到開始就做了些判斷,這個判斷我們需要先了解下Diapatcher中的三個變量:

 /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

readyAsyncCalls:在準備的異步請求。

runningAsyncCalls :正在運行的異步請求。

runningSyncCalls :正在運行的同步請求。

我們重新看回enqueue內的判斷。

如果正在運行的異步請求數 沒有爆滿且小於每個Request的請求次數,就直接放進runningAsyncCalls。不是的話就放進readyAsyncCalls。

放進runningAsyncCalls後,會調用線程池的方法去執行:executorService().execute(call);

最後執行RealCall還是會和同步請求一樣,到getResponseWithInterceptorChain攔截器的方法中去。

也就是說異步請求和同步請求的不同:只是在這個封裝的線程池處理上。

三、總結

從這個圖可以看出,不管是同步請求還是異步請求都需要通過攔截器來進行網絡數據的獲取。

而攔截器內部的操作就包含RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、RetryAndFollowUpInterceptor。

在獲取完數據後會在緩存中保存一份。最後通過線程池告訴線程執行UI的操作。

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