【OkHttp3原始碼分析】(一)Request的execute

yangxi_001發表於2017-06-20

簡單使用OkHttp3

閱讀本文需要對OkHttp3的使用有一定了解。

首先我們先看看如何簡單進行一個get請求的Request。

Request qqRequest = new Request.Builder()
                        .url("http://www.qq.com")
                        .build();
Call call = mOkHttp.newCall(qqRequest);
call.execute();//特別注意 這裡要在子執行緒執行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

或者可以使用:

Request qqRequest = new Request.Builder()
                        .url("http://www.qq.com")
                        .build();
Call call = mOkHttp.newCall(qqRequest);
//使用enquue
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        e.printStackTrace();
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.i(TAG, response.body().string());
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

execute執行流程原始碼分析

要看execute就要先看

Call call = mOkHttp.newCall(qqRequest);
  • 1
  • 1

點進去原始碼我們發現 返回了一個RealCall物件,因為Call是一個介面,而RealCall才是真正的實現類

  /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override 
  public Call newCall(Request request) {
    return new RealCall(this, request);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

現在我們找到execute方法的原始碼:

  @Override 
  public Response execute() throws IOException {
    synchronized (this) {//同步鎖,為了防止子執行緒同時呼叫而導致出現多次網路請求
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;//這裡表明,對應的Call只能執行一次
    }
    try {
      client.dispatcher().executed(this);//加入到請求佇列
      Response result = getResponseWithInterceptorChain(false);//核心程式碼!
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);//從請求佇列中移除
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

首先 我們看下一不太重點的兩行程式碼:

···
client.dispatcher().executed(this);//加入到請求佇列
···
client.dispatcher().finished(this);//從請求佇列中移除
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

這裡是用來幹嘛的?

1.Dispatcher輔助管理請求

我把相關程式碼貼出來:

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

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

  /** Used by {@code Call#execute} to signal completion. */
  synchronized void finished(Call call) {
    if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
  }

  /**
   * Cancel all calls currently enqueued or executing. Includes calls executed both {@linkplain
   * Call#execute() synchronously} and {@linkplain Call#enqueue asynchronously}.
   */
  public synchronized void cancelAll() {
    for (AsyncCall call : readyAsyncCalls) {
      call.cancel();
    }

    for (AsyncCall call : runningAsyncCalls) {
      call.cancel();
    }

    for (RealCall call : runningSyncCalls) {
      call.cancel();
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

從上面這段程式碼可以看出,它是用來將這個請求加入佇列中的,方便我們取消任務的。

2.ApplicationInterceptorChain與proceed()

搞懂了之後,我們來看看execute執行的那一行核心程式碼:

//核心程式碼!
Response result = getResponseWithInterceptorChain(false);
  • 1
  • 2
  • 1
  • 2

這裡呼叫了getResponseWithInterceptorChain方法(廢話,我夠知道)- -#。 我們來看看相關程式碼:

private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
    Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
    return chain.proceed(originalRequest);
  }

  class ApplicationInterceptorChain implements Interceptor.Chain {
    private final int index;
    private final Request request;
    private final boolean forWebSocket;

    ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
      this.index = index;
      this.request = request;
      this.forWebSocket = forWebSocket;
    }

    @Override public Connection connection() {
      return null;
    }

    @Override public Request request() {
      return request;
    }

    @Override public Response proceed(Request request) throws IOException {
      // If there's another interceptor in the chain, call that.
      if (index < client.interceptors().size()) {
        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
        Interceptor interceptor = client.interceptors().get(index);
        Response interceptedResponse = interceptor.intercept(chain);

        if (interceptedResponse == null) {
          throw new NullPointerException("application interceptor " + interceptor
              + " returned null");
        }

        return interceptedResponse;
      }

      // No more interceptors. Do HTTP.
      return getResponse(request, forWebSocket);
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

getResponseWithInterceptorChain裡面建立了一個ApplicationInterceptorChain物件,並且呼叫了它的proceed方法。

我們來看一下傳入的這個引數:0。 實際上這個0是有特殊含義的。

這裡面涉及了OkHttp3的新特性:攔截器Interceptor。 它可以新增多個攔截器,而這裡的0的意思,就是從第1個攔截器開始。

簡單瞭解完之後,我們看看關鍵的proceed程式碼

    @Override 
    public Response proceed(Request request) throws IOException {
      // If there's another interceptor in the chain, call that.
      // 判斷還有沒有攔截器? 有的話先走攔截器
      if (index < client.interceptors().size()) {
        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);//建立下一個攔截器的chain
        Interceptor interceptor = client.interceptors().get(index);//獲取攔截器
        Response interceptedResponse = interceptor.intercept(chain);//傳遞過去
        //這裡要注意,攔截器返回的Response不能為空
        if (interceptedResponse == null) {
          throw new NullPointerException("application interceptor " + interceptor
              + " returned null");
        }
        //返回結果
        return interceptedResponse;
      }

      // No more interceptors. Do HTTP.
      //當執行完攔截器之後,執行真正的Http請求。
      return getResponse(request, forWebSocket);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

相信看完上面的註釋,我相信大家也有一定的瞭解了。

有一個地方需要大家注意的,在官方文件中關於Application Interceptors有一句這樣的話:

Permitted to short-circuit and not call Chain.proceed().

意思是,我們可以在攔截器中,不呼叫proceed()。 但是proceed正是返回我們Response的方法啊!

我不太明白這個設計理念!

就此問題,我專門去OkHttp的Issues提問,有幸獲得了JakeWharton大神的回答(沒錯,那位JakeWharton) 
附上鍊接: 
What’s mean “Permitted to short-circuit and not call Chain.proceed().”?

請原諒我的渣英語。(逃

JakeWharton大神是這樣說的: 

意思是說:這樣的設計是用在Http快取裡面的。執行請求之前,我們可以先從本地讀取,看看有沒有這個請求的快取。如果沒有,我們就呼叫proceed的方法!

但是需要再次注意,就算我們不呼叫這個proceed方法,也一定要返回一個Response物件! 這樣才可以避免丟擲空指標異常。:

        if (interceptedResponse == null) {
          throw new NullPointerException("application interceptor " + interceptor
              + " returned null");
        }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

分析完上面之後,我們發現,它呼叫了:

      // No more interceptors. Do HTTP.
      //當執行完攔截器之後,執行真正的Http請求。
      return getResponse(request, forWebSocket);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

這裡才是真正執行Http請求的地方。

關於getResponse具體內容,我就不再繼續分析了….因為需要的篇幅比較大,我會在接下來的文章進行分析! 先大概瞭解是用HttpEngine這個類來進行請求的!

結束

OK!關於execute方法,就分析地差不多了。應該也講地比較清楚了,請務必搞懂上面的內容,因為enqueue的方法是呼叫execute的方法的!

所以要是看不懂execute…那enqueue也看不懂了!

下篇文章將帶來enqueue的原始碼流程分析!(雖然你看懂了execute之後,也可以自己嘗試看看enqueue的原始碼了!)

備註

關於攔截器,這不屬於本文範圍,將在後續的文章中進行講解。 
這裡也附上一些學習的連結: 
官方Wiki Interceptor

官方Wiki Interceptor中文翻譯

或者可以看我另外一篇文章(這裡是專案中的實際應用,品完官方文件,食用更佳): 
【專案重構】使用OkHttp解決Session過期導致使用者掉線的問題

-Hans 2016.4.3 21:30

轉自:http://blog.csdn.net/qq_18402085/article/details/51052167

相關文章