源碼分析OKHttp的執(zhí)行過程

OKHttp 是目前 Android 平臺主流的網(wǎng)絡(luò)請求的基礎(chǔ)框架拥褂。因此我們有必要對其源碼進行閱讀學習紊册,了解其內(nèi)部的原理放前、項目結(jié)構(gòu)、以及請求的執(zhí)行過程痊焊。

它的項目地址為:https://github.com/square/okhttp

0x00 簡單使用

先從一個簡單的官方示例來看盏袄,這是一個同步 GET 請求

public class GetExample {
  //1.http客戶端
  OkHttpClient client = new OkHttpClient();
    
  String run(String url) throws IOException {
    //2.構(gòu)造請求
    Request request = new Request.Builder()
        .url(url)
        .build();
    //3.執(zhí)行請求,獲取響應(yīng)數(shù)據(jù)
    try (Response response = client.newCall(request).execute()) {
      return response.body().string();
    }
  }

  public static void main(String[] args) throws IOException {
    GetExample example = new GetExample();
    String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
    System.out.println(response);
  }
}

可以看出這個 GET 請求操作是很簡單的薄啥。有幾個很重要的接口

  • OKHttpClient : 它代表著 http 客戶端
  • Request:它封裝了請求對象辕羽,可以構(gòu)造一個 http 請求對象
  • Response:封裝了響應(yīng)結(jié)果
  • Callclient.newCall調(diào)用后生成一個請求執(zhí)行對象Call,它封裝了請求執(zhí)行過程垄惧。

這幾個接口是程序員在使用 OKHttp 庫中經(jīng)常遇到的刁愿。

接下來將從這個示例開始閱讀 OkHttp 的源碼

0x01 Call.execute()

跟進源碼后發(fā)現(xiàn)這個方法是在 Call 中的接口

/**
 * A call is a request that has been prepared for execution. A call can be canceled. As this object
 * represents a single request/response pair (stream), it cannot be executed twice.
 */
public interface Call extends Cloneable {
  //...
  //同步執(zhí)行請求
  Response execute() throws IOException;

  //將請求加入隊列
  void enqueue(Callback responseCallback);

  //...
}

從源碼注釋知道,Call 是一個準備請求的執(zhí)行對象赘艳,它可以被取消,代表一個 “請求/響應(yīng)” 對克握,不能執(zhí)行兩次蕾管。

RealCall

Call 的實現(xiàn)類是 RealCall,因此 execute 方法

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

這個方法也不是很長菩暗,邏輯很簡單:

  • 同步鎖檢查該請求是否已經(jīng)執(zhí)行掰曾,如果沒有則標記executed = ture,否則拋出異常
  • 調(diào)用了回調(diào)函數(shù)callStart
  • okhttp客戶端調(diào)用dispatcher 將執(zhí)行請求對象
  • 調(diào)用了getResponseWithInterceptorChain 方法獲取到響應(yīng)數(shù)據(jù)Response停团,這個方法很重要旷坦,后面會繼續(xù)跟進
  • 然后是對請求失敗的回調(diào)callFailed
  • 最后還是使用dispather對象調(diào)用finished方法掏熬,完成請求

這里的邏輯還是比較清晰的,出現(xiàn)兩個重要的方法

  1. dispatcher.execute
  2. getResponseWithInterceptorChain

接下來分別看這兩個方法

0x02 Dispatcher

public final class Dispatcher {
    
  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;

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

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

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

  /** 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) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

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

可以看出Dispatcher 是一個調(diào)度器秒梅,它內(nèi)部有一個線程池executorService ,還有三個隊列旗芬,分別代表同步請求進行隊列、異步請求等待隊列捆蜀、異步請求執(zhí)行隊列疮丛。

我們發(fā)現(xiàn)調(diào)用execute方法時就是將Call對象加入到同步請求進行隊列runningSyncCalls中,而調(diào)用finished 方法則是將Call請求從隊列中移除

0x03 getResponseWithInterceptorChain

現(xiàn)在在回到RealCall 源碼中辆它,這個方法可以說是OkHttp最關(guān)鍵的部分了

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()));//處理cookie的攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));//處理緩存的攔截器
    interceptors.add(new ConnectInterceptor(client));//負責連接的攔截器
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());//添加程序員自定義的network攔截器
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));//調(diào)用服務(wù)攔截器

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

在添加了一系列的攔截器之后誊薄,又構(gòu)造了一個攔截器責任鏈,這個RealInterceptorChain 包含了所有的攔截器對象锰茉。然后調(diào)用chain.proceed方法開始執(zhí)行請求呢蔫,這時就到了RealInterceptorChain 這個類中。

0x04 RealInterceptorChain

@Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    //省略無關(guān)代碼...

    //1. 執(zhí)行攔截器責任鏈中的下一個攔截器
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    //2. 獲取當前的攔截器
    Interceptor interceptor = interceptors.get(index);
    //3. 執(zhí)行攔截飒筑,并返回響應(yīng)
    Response response = interceptor.intercept(next);

    //省略...

    return response;
  }

可以看到片吊,在proceed方法,又構(gòu)造了RealInterceptorChain并且調(diào)用了interceptor.intercept方法扬霜,

而這個方法中又會調(diào)用next.proceed方法定鸟,直至返回response。這個過程有點像遞歸調(diào)用著瓶。

0x05 Interceptor

攔截器联予,它是一個接口,內(nèi)部還有一個Chain接口

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    @Nullable Connection connection();

    Call call();

    int connectTimeoutMillis();

    Chain withConnectTimeout(int timeout, TimeUnit unit);

    int readTimeoutMillis();

    Chain withReadTimeout(int timeout, TimeUnit unit);

    int writeTimeoutMillis();

    Chain withWriteTimeout(int timeout, TimeUnit unit);
  }
}

所有的攔截器都需要實現(xiàn)這個接口材原。

0x06 異步的情況

public final class AsynchronousGet {
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();
    //調(diào)用enqueue方法沸久,并設(shè)置回調(diào)接口
    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
        //這里獲取到響應(yīng)結(jié)果數(shù)據(jù)
      }
    });
  }

然后我們再看RealCall中的enqueue方法

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //最終執(zhí)行了dispatcher的enqueue方法
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

其實是執(zhí)行了dispatcher中的enqueue方法

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

dispatcher中通過線程池來執(zhí)行AsyncCall對象,因此跟進到AsyncCall中的execute方法

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        //最終還是調(diào)用了getResponseWithInterceptorChain()S嘈贰>砜琛!
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          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 {
        client.dispatcher().finished(this);
      }
    }

發(fā)現(xiàn)最終還是執(zhí)行了getResponseWithInterceptorChain威酒,因此不管是同步還是異步窑睁、最終的流程還是一樣。

0x07 總結(jié)

  1. OKHttpClient

這是一個 http 客戶端葵孤。構(gòu)建很簡單担钮,可以使用無參構(gòu)造函數(shù)。其內(nèi)部是通過 Builder 對象進行構(gòu)建的尤仍。也可以通過其內(nèi)部靜態(tài)類 Builder 來構(gòu)建箫津,然后通過 builder 設(shè)置 OkHttpClient 構(gòu)造參數(shù)。

  1. Request

請求對象。其內(nèi)部也是使用 Builder 模式封裝了構(gòu)造的過程苏遥,通過Builder使用鏈式調(diào)用也是目前很多開源庫中常見的模式饼拍。

  1. Response

響應(yīng)結(jié)果√锾浚客戶端執(zhí)行后返回響應(yīng)結(jié)果师抄,通過 Response 可以很方便的獲取到響應(yīng)數(shù)據(jù)。

  1. Call

請求執(zhí)行诫肠∷九欤可以執(zhí)行同步或者異步的請求,分別將請求發(fā)送到dispatcher

  1. Dispatcher

調(diào)度器栋豫。其內(nèi)部有一個線程池挤安,并維護了三個隊列:同步進行請求隊列、異步請求等待隊列丧鸯、異步請求進行隊列蛤铜。

還有兩個重要的方法executeenqueue方法,分別代表同步丛肢、異步的方法围肥。這兩個方法的最終的執(zhí)行流程都是一樣的

  1. Interceptor

攔截器。攔截器在OKHttpClient中使是用責任鏈模式來實現(xiàn)的蜂怎。Okhttp 中的關(guān)鍵的流程是通過攔截器責任鏈來完成的穆刻。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市杠步,隨后出現(xiàn)的幾起案子氢伟,更是在濱河造成了極大的恐慌,老刑警劉巖幽歼,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朵锣,死亡現(xiàn)場離奇詭異,居然都是意外死亡甸私,警方通過查閱死者的電腦和手機诚些,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來皇型,“玉大人诬烹,你說我怎么就攤上這事∑唬” “怎么了绞吁?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寡键。 經(jīng)常有香客問我掀泳,道長,這世上最難降的妖魔是什么西轩? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任员舵,我火速辦了婚禮,結(jié)果婚禮上藕畔,老公的妹妹穿的比我還像新娘马僻。我一直安慰自己,他們只是感情好注服,可當我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布韭邓。 她就那樣靜靜地躺著,像睡著了一般溶弟。 火紅的嫁衣襯著肌膚如雪女淑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天辜御,我揣著相機與錄音鸭你,去河邊找鬼。 笑死擒权,一個胖子當著我的面吹牛袱巨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播碳抄,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼愉老,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了剖效?” 一聲冷哼從身側(cè)響起嫉入,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贱鄙,沒想到半個月后劝贸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡逗宁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年映九,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞎颗。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡件甥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哼拔,到底是詐尸還是另有隱情引有,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布倦逐,位于F島的核電站譬正,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜曾我,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一粉怕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抒巢,春花似錦贫贝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至型诚,卻和暖如春客燕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狰贯。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工幸逆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人暮现。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓还绘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親栖袋。 傳聞我的和親對象是個殘疾皇子拍顷,可洞房花燭夜當晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 前言 用OkHttp很久了,也看了很多人寫的源碼分析塘幅,在這里結(jié)合自己的感悟昔案,記錄一下對OkHttp源碼理解的幾點心...
    Java小鋪閱讀 1,523評論 0 13
  • Dispatcher是負責對okhttp所有的請求進行調(diào)度管理的類〉缦保可以通過Dispatcher獲取踏揣,或者取消所有...
    yuashuai閱讀 2,212評論 0 2
  • 用OkHttp很久了,也看了很多人寫的源碼分析匾乓,在這里結(jié)合自己的感悟捞稿,記錄一下對OkHttp源碼理解的幾點心得。 ...
    藍灰_q閱讀 4,305評論 4 34
  • 關(guān)于okhttp是一款優(yōu)秀的網(wǎng)絡(luò)請求框架拼缝,關(guān)于它的源碼分析文章有很多娱局,這里分享我在學習過程中讀到的感覺比較好的文章...
    蕉下孤客閱讀 3,605評論 2 38
  • “你知道嗎,在斯里蘭卡咧七,有頭會跳舞的大象衰齐。”小勇說继阻。 大象會跳舞耻涛?那種笨笨的只會鼻子吸水逗游客開心的大象會跳舞废酷,說...
    大別野主人閱讀 373評論 0 0