拆輪子:OkHttp 的源碼解析(二):流程分析

OkHttp3.7源碼分析文章列表如下:
拆輪子:OkHttp 的源碼解析(一):概述
拆輪子:OkHttp 的源碼解析(二):流程分析
拆輪子:OkHttp 的源碼解析(三):任務分發器(Dispatcher)
拆輪子:OkHttp 的源碼解析(四):攔截器(Interceptor)

1、OkHttp 的基本使用

用我們最常用的異步調用來分析:

OkHttpClient okHttpClient = new OkHttpClient();              // 第一行代碼
Request request = new Request.Builder().url("url").build();  // 第二行代碼
Call call = okHttpClient.newCall(request);                   // 第三行代碼
call.enqueue(new Callback() {                                // 第四行代碼
    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, Response response) {
    }
});

2、第一行代碼分析

主要有關的類是 OkHttpClient

OkHttpClient okHttpClient = new OkHttpClient();

該代碼作用是生成一個 OkHttpClient 對象,配置一些請求參數。
當我們 new OkHttpClient() 的時候肯定調用構造方法,先看下構造方法:

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

它這里直接調用的是帶參數的非 public 的構造方法:

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

下面很長不寫了,反正就是配置了一些參數。但是我們把鼠標往上拉,還會看到一串很長的代碼:

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
  static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
      Protocol.HTTP_2, Protocol.HTTP_1_1);

  static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
      ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);

  static {
    Internal.instance = new Internal() {
      @Override public void addLenient(Headers.Builder builder, String line) {
        builder.addLenient(line);
      }

      @Override public void addLenient(Headers.Builder builder, String name, String value) {
        builder.addLenient(name, value);
      }

      ······

      @Override public Call newWebSocketCall(OkHttpClient client, Request originalRequest) {
        return new RealCall(client, originalRequest, true);
      }
    };
  }

我們都知道在 Java 中被 static 修飾的成員變量和成員方法獨立于該類的任何對象,被類的所有實例共享。

上面源碼中為什么要這么做呢?

官方文檔中有這樣一個解釋:

Escalate internal APIs in {@code okhttp3} so they can be used from OkHttp's implementation
packages. The only implementation of this interface is in {@link OkHttpClient}.

這也是一種封裝,Internal 的唯一實現在 OkHttpClient 中,OkHttpClient 通過這種方式暴露其 API 給外部類使用。

OkHttpClient 這個類后面還有用到后面再說,我們先看到這,這行代碼其實很簡單就是生成了一個 OkHttpClient 的對象。

3、第二行代碼分析

主要有關的類是 Request

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

這行代碼的作用使用鏈式調用生成一個 Request 對象,每一個HTTP請求包含一個URL、一個方法(GET或POST或其他)、一些HTTP頭。請求還可能包含一個特定內容類型的數據類的主體部分。

將其拆分我們一個一個看:

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

3.1 對于 new Request.Builder() 分析

Builder 是 Request 的一個內部類。當我們調用:

Request.Builder builder = new Request.Builder();

我們查看源碼看是怎么走的:

  public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;
    Object tag;

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

從源碼中可以看出,如果我們未設置請求方式,直接調用 Builder 的構造方法時,默認請求方式是 "GET"。

3.2 對于 builder.url("url") 分析

  public static class Builder {

    ······

    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if {@code url} is not a valid HTTP or HTTPS URL. Avoid this
     * exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs.
     */
    public Builder url(String url) {
      if (url == null) throw new NullPointerException("url == null");

      // Silently replace web socket URLs with HTTP URLs.
      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
        url = "http:" + url.substring(3);
      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
        url = "https:" + url.substring(4);
      }

      HttpUrl parsed = HttpUrl.parse(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

    ······
}

從源碼中可以看到,如果我們傳入一個字符串的 url,如果字符串為空會拋出一個空指針異常;不為空則判斷是 http 請求還是 https 請求。

3.3 對于 url.build() 分析

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

此處也做了一個 url 的非空判斷,然后生成一個 Request 對象并將其返回,我們在看下 Request 的構造方法。

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }

4、第三行代碼分析

Call call = okHttpClient.newCall(request);

這行代碼使用上面生成的 OkHttpClient 對象來調用它的 newCall 方法,將前面的 Request 請求對象傳入,所有邏輯就在 newCall( ) 方法中。

我們又回到了 OkHttpClient 類:

  /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

這里涉及到連個新類 Call 和 RealCall,看名字就知道他們兩個有關系。
先看 Call,我刪除所有的注釋,如下:

package okhttp3;

import java.io.IOException;

public interface Call extends Cloneable {

  Request request();

  Response execute() throws IOException;

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

原來 Call 只是一個接口,Cloneable 這個接口是個空的,顯然 RealCall 是它的具體實現類。

原來我們執行網絡請求的異步 enqueue 和同步 execute 操作都定義在這。

現在跳轉到 RealCall 這個實現類,在上面我們看到傳入了三個參數,點擊去看下:

  RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    final EventListener.Factory eventListenerFactory = client.eventListenerFactory();

    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);

    // TODO(jwilson): this is unsafe publication and not threadsafe.
    this.eventListener = eventListenerFactory.create(this);
  }

5、最后一行代碼

終于到了最后一行代碼,請求服務器失敗成功和返回值都在這里,又分為同步和異步。

同步請求
call.execute();

點擊 execute() 方法的源碼如下:

  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

先判斷請求 Call 是否已經執行了,如果執行了則拋出異常,然后繼續執行, client.dispatcher().executed(this)點擊進去代碼如下:

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

可以看出同步請求就是把生成的 Call 插入同步請求的隊列 runningSyncCalls 中。
Response result = getResponseWithInterceptorChain();
返回的 result 就是網絡請求的返回數據。
最后調用 finishied 方法將 call 從同步隊列中刪除。

異步請求
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

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

源碼如下:

@Override void enqueue(Callback responseCallback, boolean forWebSocket) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}

可以看出,不管是異步還是同步,檢查這個 call 是否已經被執行了,每個 call 只能被執行一次,如果想要一個完全一樣的 call,可以利用 call#clone 方法進行克隆。

同樣先置標志位,然后將封裝的一個執行體放到異步執行隊列中。這里面引入了一個新的類AsyncCall,這個類繼承于NamedRunnable,實現了Runnable接口。NamedRunnable可以給當前的線程設置名字,并且用模板方法將線程的執行體放到了execute方法中。

兩種請求的方法

enqueue/execute 方法中涉及好幾個新的類和方法,如下:

  • 同步 execute

    • captureCallStackTrace()
    • dispatcher()
    • getResponseWithInterceptorChain()
  • 異步 enqueue

    • captureCallStackTrace()
    • dispatcher()
    • AsyncCall

我們看到不管是同步還是異步都會調用兩個相同的方法 captureCallStackTrace()dispatcher() ,先看下 captureCallStackTrace() 源碼:

  private void captureCallStackTrace() {
    Object callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()");
    retryAndFollowUpInterceptor.setCallStackTrace(callStackTrace);
  }

其實,我們根據方法的名字就知道什么意思,Stack:棧,Trace:追蹤 ,是用來跟蹤調用棧的信息的,不去深究了。

對于 dispatcher(),它涉及到一個新的類 Dispatcher,這個類有點復雜,它是一個核心重點類。在下篇文章中詳細說。

拆輪子:OkHttp 的源碼解析(三):任務分發器(Dispatcher)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容