1. 程式人生 > >OkHttp3原始碼分析第一回之請求過程

OkHttp3原始碼分析第一回之請求過程

有關Android網路請求的開源庫有很多,而OkHttp無疑是最優秀的網路請求庫,它幾乎能高效完美處理各種複雜的Http請求。說實話,這個庫還是很值得去閱讀它的原始碼的。所以我們今天來分析一下它的原始碼吧。本文基於OkHttp版本3.11.0。部分程式碼使用Kotlin語言來編寫。

一. OkHttp執行請求

首先我們來構建兩次(一個同步,一個非同步)簡單的請求:

fun testOkHttp(url: String) {
        //配置OkHttpClient所需的一些引數到一個Builder裡面
        val builder = OkHttpClient.Builder()
                .
connectTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) //然後用該builder建立一個OkHttpClient val client = builder.build() //構建一個請求 val request = Request.Builder() .url(url) .
build() //1.執行同步請求,請求執行和返回結果在同一個執行緒 //同步請求結果 val responseExecute: Response? = client.newCall(request).execute() //2.執行非同步請求,請求執行和返回結果不在同一個執行緒,返回結果以回撥的方式 client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call?, e: IOException?
) { } override fun onResponse(call: Call?, response: Response?) { //非同步請求結果 val responseEnqueue: Response? = response } }) }

以上程式碼,我們揪出幾個點,然後逐一擊破來了解OkHttp請求的整個過程。

  • OkHttpClient構建
  • newCall方法
  • Call介面和它的子類RealCall
  • 同步請求execute()
  • 非同步請求enqueue()
  • Dispatcher類

二. 原始碼分析

1. OkHttpClient構建

我們先看OkHttpClient類,該類提供了兩個構造器:

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

我們可以直接使用OkHttpClient構造器來建立一個例項,它會直接呼叫兄弟構造器:

OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    ...
}

它使用的的引數是Builder裡面預設設定的引數:

public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

當然了,我們也可以不採用預設的Builder引數,像開頭程式碼示例一樣先構建一個Builder,我們在Builder裡面自由設定自己的配置引數,然後再build出一個OkHttpClient例項:

public OkHttpClient build() {
      return new OkHttpClient(this);
    }

我們可以這麼理解,OkHttpClient裡需要的配置太多了,採用建造者設計模式可以讓呼叫者靈活配置一個自己的OkHttpClient。

2. newCall方法

我們先看newCall方法:

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

這是一個重寫了Call介面的內部介面Factory的call方法,然後呼叫的是RealCall的newRealCall方法。RealCall類實現了Call介面,newRealCall方法返回了一個Call型別的例項。所以到目前為止,我們知道同步請求execute()和非同步請求enqueue()實際是在Call介面的子類RealCall中執行的。那我們接下來重點來了解一下這個Call介面和它的子類RealCall。

3. Call介面和它的子類RealCall

public interface Call extends Cloneable {
    ...
    //同步請求,直接返回Response
    Response execute() throws IOException;
    //非同步請求,通過回撥返回Response
    void enqueue(Callback responseCallback);
    ...
    //OkHttpClient中的newCall()方法是繼承這個內部介面來實現的
    interface Factory {
    	Call newCall(Request request);
  	}
}

可以看到Call介面向外部暴露了一個工廠介面來"生產"自己,是不是很熟悉的工廠設計模式中的工廠介面設計。Call介面只有一個子類RealCall類,我們直接調到RealCall類中來看重頭戲execute()和enqueue()。

4. 同步請求execute()

直接上原始碼分析:

@Override public Response execute() throws IOException {
    synchronized (this) {
      //只能呼叫一次
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      //又跑到dispatcher.executed(),黑人問號臉?
      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 {
      //最後跑到dispatcher呼叫了finished()
      client.dispatcher().finished(this);
    }
  }

直接總結:

1.execute()只能被呼叫一次。

2.client.dispatcher().executed(this);這句幹了什麼?

private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

並沒有很多動作,僅僅將這個Call加入到佇列runningSyncCalls中。後面我們再來說說這個佇列的事。正真的執行請求其實還是在RealCall中執行的。請求結果response也是在建立Call的執行緒中返回的。

3.Response result = getResponseWithInterceptorChain();這裡才是真正幹活了。

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, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

上面程式碼先是添加了各種攔截器。先加一些從okHttpClient外部配置的一些攔截器,如果你用過OkHttpClient+Retrofit2,你很容易想到我們構建OkHttpClient的Builder的時候是會加一些我們自定義的攔截器的(這些我們自定義的攔截器是最先上車的),後面又加一些OkHttp自帶的攔截器:RetryAndFollowUpInterceptor…(我們先忽略掉這些攔截器,這正是這個庫設計的精髓,這是後面我們要重點分析的東西,這一回合我們只分析請求過程)。這些裹上各種攔截器的請求作為一個最終的請求去伺服器端申請資料了。

4.client.dispatcher().finished(this);

這裡finished呼叫的是Dispatcher類的finish方法。同步請求裡面沒有過多操作,只是移除請求結束後將該請求從runningSyncCalls佇列中移除。

/** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

  /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
       //將該請求從runningSyncCalls佇列中移除
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
       //同步請求結束不會執行 promoteCalls()
      if (promoteCalls) promoteCalls();
       //返回同步請求佇列和非同步請求佇列裡面的請求總數
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }
	//下面這個if判斷基本不會執行。因為原始碼中idleCallback一直為空
    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }
public synchronized int runningCallsCount() {
    return runningAsyncCalls.size() + runningSyncCalls.size();
  }

至此,同步請求的過程我們大概明白了。請求執行時在RealCall中直接通過getResponseWithInterceptorChain()執行的,返回的結果也是在建立RealCall的執行緒中。Dispatcher中只是將請求加入正在執行的同步佇列中。請求結束後最終呼叫finished()方法來移除已經結束的請求。

5. 非同步請求enqueue()

跑到Dispatcher類裡面去執行非同步請求了:

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //呼叫Dispatcher裡面的enqueue方法,並傳入一個AsyncCall物件
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

enqueue()裡面做了兩件事,判斷條件合格,將請求加入正在執行的非同步佇列,否則,將請求加入等待的非同步佇列。

synchronized void enqueue(AsyncCall call) {
    //如果正在執行的非同步佇列請求數<64&&同一主機請求數<5,執行下面操作,將當前請求加入正在執行的非同步佇列,執行緒池開始執行請求。
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

先看下AsyncCall類,這是RealCall的內部類:

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;
    }
	//又看到了熟悉的execute()方法
    @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;
          //這裡將response傳到回撥裡面
          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 {
        //這裡也跟同步請求一樣呼叫finished()方法。不過具體執行可不一樣。
        client.dispatcher().finished(this);
      }
    }
  }

調到Disptacher類裡面來看finished()方法,與同步不一樣的是,這裡呼叫了promoteCalls()方法。其實promoteCalls()方法相當於將結束的請求移除,然後推動下一個請求,直到佇列裡面沒有請求了。

void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

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!");
      //非同步請求走了if判斷
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

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

private void promoteCalls() {
    //如果非同步佇列裡面請求數超過64了,return
    if (runningAsyncCalls.size() >= maxRequests) return;
    //如果等待的非同步佇列裡面是空的,return
    if (readyAsyncCalls.isEmpty()) return;
	//那麼非同步佇列裡面請求數不滿64&&等待的非同步佇列裡面還有等待的請求,就開始執行下面操作
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();
	  //如果請求同一個主機數<5,等待佇列先remove當前請求,其實就是結束的請求,然後非同步佇列加入等待佇列中的下一個請求,執行緒池開啟一個執行緒開始執行這個剛加入的請求。
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return;
    }
  }

private int runningCallsForHost(AsyncCall call) {
    int result = 0;
    for (AsyncCall c : runningAsyncCalls) {
      if (c.get().forWebSocket) continue;
      if (c.host().equals(call.host())) result++;
    }
    return result;
  }

整個過程其實非常明瞭了。請求是通過Dispatcher類裡面的執行緒池來推動的,執行一個AsyncCall任務,AsyncCall來執行請求getResponseWithInterceptorChain(),整個請求過程是發生線上程池中的,請求返回的結果是通過ResponseCallback回撥傳到外面(這裡的外面一般指主執行緒)。請求的時候是先判斷條件加入到正在執行的非同步佇列,請求結束後呼叫promoteCalls()移除當前結束的執行緒並還存在下一個請求的條件下,再次呼叫Dispatcher類裡面的執行緒池推動下一個請求。