OkHttp源碼解析(一)同步請求和異步請求

        接下來幾篇文章,將會進行OkHttp的源碼解析。還記得去年花了半天時間去進行Volley的源碼解析,其實去年那篇文章我也說過,Volley已經算是一個過時的網絡請求框架。因此,接下來,我會選擇主流的網絡請求框架OkHttp進行源碼解析。今天,主要從OkHttp的兩種請求方式開始說起,也就是同步請求和異步請求。

一、前言

        作爲一名優秀的程序員,一定要有分析源碼的經驗和能力。我們經常在面試中被問及源碼,或許我們會在心裏去反對這一現象,覺得框架就是拿來用的,我管他怎麼實現的幹嘛?我估計很多朋友跟我一樣,有過這樣的想法。其實,面試官之所以問及我們源碼實現,其實是想從側面考察我們的一種軟件架構能力。爲什麼這麼說?因爲,但凡是GitHub上優秀的框架,都是在很多優秀的工程師的努力下,一步一步地豐富和發展起來的。當然,也經過了無數工程師的測試。可以說,這些框架從架構設計到代碼實現,都是非常優秀,非常值得我們借鑑的。當然,我們更多地是去學習框架的架構設計和代碼實現,並不代表我們學習了也一定能寫出這麼優秀的框架。

一、同步請求

        在這一章節,我會通過一次簡單的okhttp同步請求方式,去跟蹤一下源碼的實現。首先,我們看一下okhttp同步請求的簡單實現。如下圖所示,我在跟蹤和學習源碼過程中,也在我的示例代碼中去做了相應的筆記:

private void syncRequest() {
        //Builder():建造者模式,裏面封裝了okhttpclient初始化所需要的所有參數
        OkHttpClient client = new OkHttpClient.Builder().readTimeout(5,TimeUnit.SECONDS).build();

        //Builder():建造者模式,指明get請求方式,保存headers的信息
        Request request = new Request.Builder()
                .url("http://www.baidu.com")
                .get()
                .build(); //構建攜帶請求信息的Request對象
        Call call = client.newCall(request);//RealCall,傳入request,構建出Call對象

        try {
            Response response = call.execute();//dispatcher.executed():把請求加入到同步請求隊列,獲取到response後,移除掉同步請求。
            if(response != null){
                Log.d("TTTT",response.body().string());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

        對於okhttp的同步請求,我們大致可以概括爲如下四個步驟:

(1)創建okhttpclient對象:okhttpclient對象的創建,採用的是建造者模式。我們知道,建造者模式一般應用於非常複雜的對象的創建。okhttpclient對象本身就是非常複雜的,它具有很多的參數。我們跟蹤源碼看一下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;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

(2)構建Request對象:Request對象的創建,也是使用建造者模式。我們看一下Builder(),指明瞭請求的方式是get,並且創建了一個Headers對象。

public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

(3)創建Call對象:通過上面的client和request對象,創建一個call對象。我們跟進newCall()方法:

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

        注意,在這裏,實際上調用的是RealCall.newRelCall方法,我們繼續跟進去,創建了一個RealCall對象。這個RealCall對象,就是具體執行請求的call對象。

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

(4)執行call的execute方法,我們看一下源碼實現:

  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
      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 {
      client.dispatcher().finished(this);
    }
  }

        最開頭的代碼,是爲了同步請求只執行一次。後面關鍵的代碼就是dispatcher.executed()方法,通過調用這個方法去執行請求,我們跟進去看一下,很簡單的實現,就是把請求加到了同步請求隊列。

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

        在執行完同步請求獲取response後,我們看到,在finally語句塊中,執行了dispatcher的finished方法。我們跟進去看一下具體實現:

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

    boolean isRunning = promoteAndExecute();

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

        首先,就是執行了remove(call)方法,把call移除,這也是保證了同步請求只執行一次。

二、異步請求

        異步請求也可以分爲四個步驟,而且前三個步驟跟同步請求是一樣的。因此,我們只跟蹤一下第四個步驟,也就是異步請求的源碼。在這裏,我把異步請求的代碼貼一下:

    private void diffRequest() {
        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) {
                
            }

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

            }
        });
    }

        通過上面的示例代碼,大家可以發現,前三步確實和同步請求一模一樣。唯一的區別就是第四步,也就是真正執行請求的方法:call.enqueue。

        接下來,我們去RealCall中看一下enqueue方法的具體實現,代碼如下:

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

        和同步請求一樣,最開始會通過synchronized去保證我們的請求只執行一次。最關鍵的實現仍然是最後面的dispatcher.enqueue,我們跟蹤一下這個方法:

  void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);
    }
    promoteAndExecute();
  }

        把call加入就緒的異步請求隊列。這個和同步請求的實現不一樣,同步請求是直接加入了runningSyncCalls,而異步請求是放進了readyAsyncCalls。我們看一下官方對這兩個隊列的註釋:

  /** 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<>();

        (1)readyAsyncCalls:意思很明確,是他們"將會"被按順序執行。因此,他們只是被加進了一個就緒態的隊列,而不是立刻被執行。

       (2)runningAsyncCalls:正在執行的異步請求隊列。也就是說該隊列裏的請求是正在被執行或者被取消掉的沒執行完的異步請求。

      (3)runningSyncCalls:正在執行的同步請求隊列。也就是說該隊列裏的請求是正在被執行或者被取消掉的沒執行完的同步請求。

        可能有些朋友早就有這麼個疑問:同步請求和異步請求的區別是啥?其實,通過二者使用方法的區別,我們也能歸納出來。同步請求是直接加入正在執行隊列,也就是立刻執行。而異步請求是被加入到就緒隊列,是要過一段時間纔會加到正在執行隊列的。因此,異步請求的方法,有失敗和成功的異步回調。

        在把異步請求添加到就緒隊列後,後面還有一個promoteAndExecute方法,這是真正執行異步請求的方法,我們還是跟進去:

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

        這個方法,首選他會去遍歷就緒隊列中的call,在遍歷的過程中,會去做兩個判斷:(1)如果正在執行隊列的請求數超過了最大的請求數容量則break,如果一個Host的請求比允許的每個host的請求容量小,那麼將會continue,並且把異步請求添加到兩個隊列:可執行隊列和正在執行異步請求隊列。後面,去遍歷可執行隊列中的請求,並且通過executeOn方法,去執行異步請求,跟一下executeOn方法:

void executeOn(ExecutorService executorService) {
      assert (!Thread.holdsLock(client.dispatcher()));
      boolean success = false;
      try {
        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!
        }
      }
    }

        這是RealCall的方法,我們看到了executorService,這是一個線程池。也就是說,RealCall通過線程池去執行請求。線程池的原理我們在這裏不再多說,我們看一下此線程池的具體參數,跟蹤源碼:

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

        corePoolSize爲0,maxPoolSize爲Integer的最大值,keepAliveTime爲60秒。在這裏,有一個很有意思的設定。爲什麼說有意思呢,因爲corePoolSize設置爲0。熟悉線程池的朋友應該知道,當線程池中執行完所有的任務,並且在keepAliveTime的時間裏沒有來新的任務時,如果核心線程數不爲0,那麼線程池將無法關閉。如果設置核心線程數爲0,那麼到了60秒之後,線程池自動關閉。

        最後,總結一下這篇文章的內容。通過同步請求和異步請求的實現,我們跟蹤了源碼的實現,並且通過源碼的實現總結出了同步請求和異步請求的區別。後面的文章,將會對okhttp另外的一些比較關鍵的地方進行源碼分析。例如,我們在本章中多次提到的dispatcher。

 

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