okhttp框架 同步請求流程和源碼分析

創建Client的外部調用

OKHttpClient client = new OkHttpClient.Builder().readTimeOut(5,TimeUnit.SECONDS).build();

先來看下OkHttpClient的內部類Builder的構造方法:

    public Builder() {
      dispatcher = new Dispatcher(); //okhttp請求的分發器,由它決定異步請求是直接處理,還是進行緩存等待。
//當然,他對同步請求並沒有做太多操作,只是把它的同步請求放到隊列中去執行。
      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();//它其實是一個連接池,我們可以這樣理解,客戶端和服務端之間的連接,
//我們可以把它抽象爲一個connection,
//而每一個connection,我們都會把它放在connectionPool這個連接池當中,他來進行一個統一的管理,
//當你請求到的url是相同的時候,你就可以選擇複用,這是作用之一;
//另一個作用就是connectionPool這個類它實現了哪一些網絡連接它給保持打開狀態,哪一些是可以用來複用的,
//這些策略的設置,也是需要connectionPool這個連接池來進行管理的。
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

這裏這麼多參數在後面的一些請求流程中都會使用到,這就是Builder創建對象的模式,用Builder對象來封裝OkHttp初始化所需要的參數,然後傳遞我們的Builder對象到OkHttpClient的構造方法裏,完成它的整個對象屬性的初始化。

當你創建一個對象時,如果這個對象需要很多的參數,這時候,你可以使用到Builder這個創建模式,是一個很好的解決辦法。

 

創建Request請求報文信息類

Request的創建方式也是通過Builder來創建的,然後,通過鏈式調用,給Request指定url,或者頭部,以及方法等等。

外部調用如下:

Request request = new Request.Builder().url("http://www.baidu.com").get().build();

先來看下Request的內部類Builder的構造方法。

    public Builder() {
      this.method = "GET";//指定了請求方式爲GET,它是默認的
      this.headers = new Headers.Builder();//創建了Headers的Builder對象來保存它的頭部信息
    }

接下來看下build()方法,在build()方法裏面,他是直接創建一個Request對象,然後把當前的build對象傳遞過去,它的意思是把之前配置好的請求方式,url,頭部,都賦值給Request對象。

    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }

還有Request的構造方法。Request構造方法就是爲其指定了請求方式,以及請求的網絡地址url,以及它的頭部信息等等,這樣就完成了OkHttp同步請求的前兩步。

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.duplex = builder.duplex;
    this.tags = Util.immutableMap(builder.tags);
  }

 

創建Call對象

外部調用:

Call call = client.newCall(request);

它是通過client的newCall來進行實現的。

  /**
   * Prepares the {@code request} to be executed at some point in the future.
   * Call類是一個接口
   * Call類是一個準備執行的請求,可以被取消,代表着一個單一的請求/響應流,不能被執行兩次。
   * New一個Call的子類RealCall去執行execute方法
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

由於Call是一個接口,所以它的實際操作都是在它實現類RealCall中所做的,下面來看一下RealCall中是怎麼做的。

下面來看下RealCall.java中是怎麼做的。

    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對象,然後,還賦值了一個Listener,就返回了。

它究竟做了哪些工作呢?我們到RealCall 的構造方法中看一下。

    private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        this.client = client;
        this.originalRequest = originalRequest;
        this.forWebSocket = forWebSocket;
        this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
        this.timeout = new AsyncTimeout() {
            @Override
            protected void timedOut() {
                cancel();
            }
        };
        this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
    }

RealCall其實是持有了前兩步初始化好的okHttpClient,originalRequest,同時,還賦值了一個重定向攔截器。

不管是同步,還是異步請求,它都是調用了client.newCall這個方法來進行創建的,然後我們通過創建好的Call對象進行操作。

 

使用execute完成同步請求

Response response = call.execute();

看看Call.java的execute()方法做了哪些操作。

  /**
   * Invokes the request immediately, and blocks until the response can be processed or is in
   * error.
   *
   * <p>To avoid leaking resources callers should close the {@link Response} which in turn will
   * close the underlying {@link ResponseBody}.
   *
   * <pre>{@code
   *
   *   // ensure the response (and underlying response body) is closed
   *   try (Response response = client.newCall(request).execute()) {
   *     ...
   *   }
   *
   * }</pre>
   *
   * <p>The caller may read the response body with the response's {@link Response#body} method. To
   * avoid leaking resources callers must {@linkplain ResponseBody close the response body} or the
   * Response.
   *
   * <p>Note that transport-layer success (receiving a HTTP response code, headers and body) does
   * not necessarily indicate application-layer success: {@code response} may still indicate an
   * unhappy HTTP response code like 404 or 500.
   *
   * @throws IOException if the request could not be executed due to cancellation, a connectivity
   * problem or timeout. Because networks can fail during an exchange, it is possible that the
   * remote server accepted the request before the failure.
   * @throws IllegalStateException when the call has already been executed.
   */
  Response execute() throws IOException;

execute()是一個接口,再來看下它的具體實現,這是RealCall所做的具體實現。

    /*
    同步方法,不能在UI線程執行
     */
    @Override
    public Response execute() throws IOException {
        synchronized (this) {
            //是否執行過,如果執行過,拋出異常
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        timeout.enter();
        eventListener.callStart(this);
        try {
            //調用Dispatcher的executed方法將該Call加入到一個雙端隊列裏
            client.dispatcher().executed(this);
            //返回本次的Response對象
            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 {
            //從已經執行的雙端隊列中移除本次Call
            client.dispatcher().finished(this);
        }
    }

在這其中的同步代碼塊中,它首先判斷execute這個標誌是否爲true,它的意思是同一個http請求只能執行一次,沒有執行過,他就會在下面把execute置爲true,如果執行過,他就會拋出這個異常。

        synchronized (this) {
            //是否執行過,如果執行過,拋出異常
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }

接下來,他會捕捉一些Http請求的異常堆棧信息。

        captureCallStackTrace();

然後,它會開啓了一個監聽事件,每當我們的call調用execute方法,或者enqueue方法,就會開啓這個Listener。

        eventListener.callStart(this);

調用dispatcher()只是做了一個返回dispatcher對象的操作,dispatcher就是我們請求的分發器。

            //調用Dispatcher的executed方法將該Call加入到一個雙端隊列裏
            client.dispatcher().executed(this);

繼續進入這個executed方法。同步請求,它其實通過execute方法把它添加到隊列之中,完成了這個操作。

(在okhttp3-master/app/src/main/java/okhttp3/Dispatcher.java)

  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

關於Dispatcher,它的作用就是維持Call請求發給它的狀態,同時,它也維護了一個線程池,用於執行網絡請求,而Call這個請求在執行任務的時候,通過Dispatcher這個分發器類,把它的任務推到執行隊列當中,然後來進行操作。

executed()方法主要是將我們的請求加入到同步請求隊列當中,這個同步請求隊列是在哪裏定義的呢?它其實是在Dispatcher.java中定義的。

  //正在運行的同步任務,包含取消尚未完成的調用
  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

繼續接下來execute()的同步方法,再獲取response,這個其實是攔截器鏈的方法,在這個類內部會一次調用攔截器進行操作。

            //返回本次的Response對象
            Response result = getResponseWithInterceptorChain();

往下走,在finally{}裏,它會主動地回收它的某些同步請求。

        finally {
            //從已經執行的雙端隊列中移除本次Call
            client.dispatcher().finished(this);
        }

進入finished方法看看,在Dispatcher.java裏面。

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

它又調用了另外一個Finish方法,把正在執行的同步請求傳入進來。

  private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
      //移除本次call。
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }

    boolean isRunning = promoteAndExecute();

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

它首先做的操作就是把隊列中移除這個請求,如果不能移除,就拋出異常。

    synchronized (this) {
      //移除本次call。
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }

表示Dispatcher分發器中沒有可運行的請求了,纔可調用run方法。

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

關於請求的判斷在這個方法裏。

  private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      //循環判斷準備請求隊列是否還有請求
      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.
        //從準備隊列裏移除自身。
        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;
  }

在同步請求中,dispatcher分發器做的事情非常簡單,一是保存同步請求;二是移除同步請求。

 

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