OkHttp徹底解析之總體調用流程

前言:閱讀好的代碼如同觀賞美景同樣的美妙

OkHttp是一個Square公司在github開源的Java網絡請求框架,很是流行。OkHttp 的代碼並非特別龐大,代碼不少巧妙的實現,很是值得學習。
建議下載一個OkHttp的源碼,使用 Idea 或者 Source Insight 閱讀 ,跟隨本文一塊兒閱讀。java

OKHttp的全局概覽:

分析一個複雜的框架,能夠先
分析簡單的調用流程
而後分析全局的類之間的關係android

OkHttp 的類圖

來自:http://frodoking.github.io/2015/03/12/android-okhttp/git

如何編寫一個複雜的框架,OkHttp給咱們一個好的示範,這符合設計模式中的門面模式,框架的核心是 OkHttpClient 。
使用者的全部操做都是經過操做OkHttpClient 實現,OkHttpClient 下面有不少子模塊,OKHttpClient 知道全部子模塊的實現,OKHttpClient調用各個子模塊完成功能,各個子模塊是獨立的,
它們不知道OKHttpClient的存在。github

OkHttp流程圖

(from piasy 大神)web

<img src="bc55d9e1-5392-4f7f-8aab-78f0b1ffda5f_files/6869cc51-abb8-4c46-9784-a10ea0239d07.png" border="0" alt="" name="" width="548" height="876" class="">

描述設計模式

雖然 OKHttpClient 很複雜,可是邏輯仍是很簡潔的。
能夠劃分了三個階段 服務器

  • 1.OKHttpClient +Request 構造 RealCall
  • 2.RealCall 直接同步執行或者進入 異步隊列,統一由Dispatcher分發
  • 3.經過Interceptor的責任鏈,層層調用,最後獲取 Response

OKHttp最簡單的用法是:

OkHttpClient client = new OkHttpClient();

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

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

這裏,咱們所使用的是最簡單的功能,使用的類只有三個OKHttpClient,Request ,Response。
能夠說,OKHttp從設計的時候就考慮了易用性
一個網絡請求框架,最簡單的只須要
有OkHttpClient,Request,Response就能發起一次請求和響應了cookie

下面咱們就整個發起請求,接收Response的過程進行分析:網絡

1.0 建立 OkHttpClient

這是發起全部調用Call的工廠,包括髮送HTTP Request ,和接收 Response
因爲OkHttpClient 內部實現的鏈接池,線程池,等,因此
每一個應用通常只須要一個Single 單例的OKHttpClient便可,複用這個單例進行Http請求,不須要擔憂單例的擴展性問題,很是的簡單易用而且擴展性良好(由於OKHttp 靈活使用的建造器模式 :OkHttpClent.Builder):app

例如:

若是你只須要默認配置的OKHttpClient:
那麼,
你只須要:
OkHttpClient client=new OkHttpClient();
爲啥會沒有Builder呢?由於構造函數OkHttpClient() 已經默認使用默認的new Buidler();
其實

public OkHttpClient() {
    this(new Builder());
  }

  private OkHttpClient(Builder builder) {
   this.dispatcher = builder.dispatcher;
  //省略代碼
  }

若是不使用默認配置,那麼咱們沒法使用 OkHttpClient(Builder builder)
這個私有的方法new 一個OKHttpClient實例的
能夠這樣:
OkHttpClient.Builder bulider=new OKHttpClient.Builder();
OkHttpClient client=buider.xxx().xxx().build();

那麼單例的OKHttpClient會不會影響OkHttpClient的擴展性呢?
不會,咱們之因此要單例一個OKHttpClient,是應該不重複建立鏈接池和線程池

也就是說,能夠全局定義一個OkHttpClient ,在須要特殊配置一個OKHttpClient的時候,這個局部的OKHttpClient
引用的鏈接池和線程池是複用自全局單例的就行;
如:

OkHttpClient eagerClient = client.newBuilder()
        .readTimeout(500, TimeUnit.MILLISECONDS)
        .build();
    Response response = eagerClient.newCall(request).execute();

具體實現是:經過全局的OkHttpClient 單例new屬性同樣的的Builder,而後給這個Builder單獨配置局部的屬性
後,再經過這個Builder建立一個局部的OKHttpClient便可

public Builder newBuilder() {
    return new Builder(this);
  }

Builder(OkHttpClient okHttpClient) {
      this.dispatcher = okHttpClient.dispatcher;
      this.proxy = okHttpClient.proxy;
       //省略代碼

  }

1.1 建立Request

Request request = new Request.Builder()
      .url(url)
      .build();

一樣使用了Builder模式

注意:Request包含的信息只有 url ,body ,header,tag,method等基礎的信息

1.2經過OKHttpClient經過new Call(Request request)建立一個RealCall

OkHttpClient:

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

1.3建立 RealCall

OkHttp的網絡請求分爲同步和異步兩種狀況:
*同步:
直接調用
RealCall 的 execute的方法,經歷一系列的攔截器 Intercepter,返回Response
*異步:
經過RealCall的enqueue方法,建立一個AsyncCall,把這個AsyncCall,經過Client的Dispatcher
分發下去

1.4 同步執行請求和異步執行在Dispatcher的分發過程:

1 同步請求

  • RealCall.java
@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);//經過Dispatcher記錄這個在
       //最重要是這一句,經過攔截器的調用鏈獲取Response,同步執行
      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);//結束後,Dispatcher負責清理這個記錄
    }
  }

2 異步請求:

利用構造 responseCallback 構造一個RealCall的內部類 AsyncCall的對象,
而後把它插入 Dispatcher的隊列中

  • RealCall.java
@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
  • AsyncCall.java
    由於AsyncCall是RealCall的內部類 ,因此它的對象,對應的外部類RealCall的實例,而且它實現了Runnable接口,因此能夠在線程池執行
final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException 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 {
        client.dispatcher().finished(this);
      }
    }
  }
  • Dispatcher.java
//注意這是一個同步方法
  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
       //爲了不太多請求同時執行,這裏有兩個限制,
      //同時請求的最大數目不能超過 maxRequests ,同時對於每個主機host(服務器),同時請求數目不能超過maxRequestsPerHost
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
       //不然就加入一個準備
      readyAsyncCalls.add(call);
    }
  }

//同步方法 獲取一個單實例的 線程池
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;
  }

//在一個Call執行結束後
  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
       //須要取出新的Call執行,調用 promoteCalls() 自動執行 N個Call直到達到數目限制
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

   //主動取出就緒的Call 執行
  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

1.5 Interceptor鏈式調用攔截的原理和流程

在OkHttp中,好比咱們有各類需求,須要透明壓縮Gzip,咱們的請求的Request只有咱們的設定的內容,須要添加
Http協議的的Header的固定的頭信息,還要容許用戶能夠編寫本身的邏輯插入調用的過程當中,這裏就須要瞭解OkHttp的鏈式調用攔截的邏輯
首先,假設整個請求響應是一個黑盒子

咱們
輸入的是 application request

輸出 application 的 response

中間經歷了一些了的Interceptors,有OkHttp本身的定義的,也能夠有自定義的:

如:

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

由源代碼能夠看到,建立了一個 List
保存全部的攔截器
而後利用這個攔截器列表構建了一個 index=0(標識當前的Interceptor) 調用鏈 Interceptor.Chain chain = new RealInterceptorChain(...,0,...),
調用chain.proceed(originalRequest),傳入最原始的的request ,返回對應的response
下面是chain.proceed的源碼:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !sameConnection(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;

其中:最重要的代碼以下:
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);

首先建立一個next的Chain
而後根據 index獲取當前的攔截器
調用Interceptor.intercept(next),傳入下一個 nextChain,而且執行這個攔截邏輯

注意有兩種狀況,intercept

攔截器能夠決定直接返回reponse結束全部的鏈式調用過程,仍是繼續調用nextChain.proceed執行下一個攔截調用的過程,返回response

最重要的Interceptor :CallServerInterceptor

這個攔截器是最後一個攔截器,它負責發送請求到服務器

/** This is the last interceptor in the chain. It makes a network call to the server. */
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();

    realChain.eventListener().requestHeadersStart(realChain.call());
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), 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();
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        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 = 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.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;
  }

  static final class CountingSink extends ForwardingSink {
    long successfulCount;

    CountingSink(Sink delegate) {
      super(delegate);
    }

    @Override public void write(Buffer source, long byteCount) throws IOException {
      super.write(source, byteCount);
      successfulCount += byteCount;
    }
  }
}

總結

OKHttp總體的調用流程仍是很清晰的,能夠看到做者的功力。咱們在分析的時候重點關注一個請求從發起到響應結束的過程就能更容易理解。

相關文章
相關標籤/搜索