3.OkHttp請求流程分析

Call和RealCall

經(jīng)過上面的初始化之后 okhttpClient 調用public Call newCall(Request request) 方法去構建一個Call畏纲,

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

可以看到 真實的Call是RealCall ,get知識點 一般來說邑茄,一個組織或者個人的代碼風格是差不多的 這里面Call的實現(xiàn)類是RealCall十兢,其他的應該也是Real開頭的。

這里我們發(fā)現(xiàn)他把OkHttpClient和Reques傳了過去,他的構造方法是

  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.  
    // 這是不安全的發(fā)布,不是線程安全的议双。
    this.eventListener = eventListenerFactory.create(this);
  }

這里面比較面生的是的是RetryAndFollowUpInterceptor 按照字面意思重試和跟進攔截器 進去大概看一下

/**
 * This interceptor recovers from failures and follows redirects as necessary. It may throw an
 * {@link IOException} if the call was canceled.
 * 這個攔截器從故障中恢復,并根據(jù)需要遵循重定向捉片。如果呼叫被取消平痰,它可能會拋出IOException。
 */
public final class RetryAndFollowUpInterceptor implements Interceptor {
  /**
   * How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,
   * curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
   * 我們應該嘗試多少次重定向和認證挑戰(zhàn)伍纫? Chrome遵循21次重定向; Firefox宗雇,curl和wget遵循20; Safari遵循16; HTTP / 1.0建議5。
   */
  private static final int MAX_FOLLOW_UPS = 20;

  private final OkHttpClient client;
  private final boolean forWebSocket;
  private StreamAllocation streamAllocation;
  private Object callStackTrace;
  private volatile boolean canceled;

  public RetryAndFollowUpInterceptor(OkHttpClient client, boolean forWebSocket) {
    this.client = client;
    this.forWebSocket = forWebSocket;
  }
  
  (....此處省略以后的代碼)

果然和字面意思一樣莹规,這面看參數(shù)的話最大支持20次的重定向赔蒲。后面的暫時不需要看 等用到的時候再看也不遲,避免陷入之間樹木不見森林的坑

后面又用工廠方法創(chuàng)建了一個EventListener 的類访惜,看字面意思就是時間的監(jiān)聽類看里面的方法

Factory
fetchStart
dnsStart
dnsEnd
connectStart
secureConnectStart
secureConnectEnd
connectEnd
requestHeadersStart
requestHeadersEnd
requestBodyStart
requestBodyEnd
responseHeadersStart
responseHeadersEnd
responseBodyStart
responseBodyEnd
fetchEnd

通過這個事件我們大致能看出來OkHttp請求的流程嘹履,然后回到RealCall ,之后調用的是一個execute或者是enqueue 我們在Android項目里由于主線程是不允許有網(wǎng)絡請求的债热,所以我們先來搞enqueue,話不多說幼苛,進去看

@Override 
public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    //捕獲呼叫堆棧跟蹤
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

Already Executed 這個異常大家在平時用的時候應該偶爾會碰到窒篱,原因看到了吧。當你的這個請求應在運行的時候你在去調用的時候就異常了

然后第二個是捕獲呼叫堆棧跟蹤器舶沿,這個就忽略掉墙杯,看真正的重頭戲

前方高能預警,提起精神看

Dispatcher

相關的更詳細的分析在第五篇 OkHttp的請求調度分析

調用的OkHttpClient 的dispatcher的enqueue方法括荡,dispatcher的初始化是在OkHttpClient的Builder里面 看上面的代碼 是直接new 了一個 Dispatcher 高镐,我們去dispatcher里看

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private 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<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }
  
  (....此處省略N行代碼)
  
   synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
}

我們看到他的構造函數(shù)就是一個空的 然后全局變量的話有個線程池,和三個雙端隊列 有可能有同學不知道Deque是什么畸冲,deque 即雙端隊列嫉髓。是一種具有隊列和棧的性質的數(shù)據(jù)結構。雙端隊列中的元素可以從兩端彈出邑闲,其限定插入和刪除操作在表的兩端進行算行。

我們看到enqueue里面就是操作運行異步調用包括尚未完成的取消請求的runningAsyncCalls,他的判斷條件是:

1.當前隊列里面的請求數(shù)量小于最大請求數(shù)也就是64

2.當前隊列里面的鏈接的總host數(shù)量小于最大請求Host數(shù)

如果條件成立就添加到這個隊列里面苫耸,否則的話就添加到readyAsyncCalls里州邢,也就是按照他們將要運行的順序進行準備就緒的異步調用的隊列

加入到運行隊列里后,執(zhí)行executorService().execute(call);方法這個方法就是個new出了一個線程池褪子,然后執(zhí)行

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

可能關于線程池的一些東西大家不是特別清楚 這里稍微解釋一下量淌,首先是他的構造函數(shù)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, 
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) 
corePoolSize: 線程池維護線程的最少數(shù)量 
maximumPoolSize:線程池維護線程的最大數(shù)量 
keepAliveTime: 線程池維護線程所允許的空閑時間 
unit: 線程池維護線程所允許的空閑時間的單位 
workQueue: 線程池所使用的緩沖隊列 
handler: 線程池對拒絕任務的處理策略

上面的SynchronousQueue可能一般同學看的不是特別熟悉這里解釋一下:

SynchronousQueue是一個沒有數(shù)據(jù)緩沖的BlockingQueue骗村,生產(chǎn)者線程對其的插入操作put必須等待消費者的移除操作take,反過來也一樣呀枢。

SynchronousQueue的一個使用場景的典型就是在線程池里叙身。Executors.newCachedThreadPool()就使用了SynchronousQueue,這個線程池根據(jù)需要(新任務到來時)創(chuàng)建新的線程硫狞,如果有空閑線程則會重復使用信轿,線程空閑了60秒后會被回收。執(zhí)行是調用execute方法残吩。

峰回路轉 财忽,回到ReallCall的enqueue里面

這里執(zhí)行的正式AsyncCall,new AsyncCall(responseCallback) AsyncCalls是RealCall的一個內部類泣侮,繼承NamedRunnable即彪,NamedRunnable是一個實現(xiàn)了Runnable接口的抽象類,

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

這里他做了兩件事

  1. 給當前線程設設置了個名字
  2. 新增了一個抽象方法execute

把握住這兩個 再來看AsyncCall

AsyncCall

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      //命名規(guī)則 OkHttp+協(xié)議名+域名
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        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 {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        //調用dispatcer的finshed方法活尊,(重要隶校,在下文的請求調度分析會將)
        client.dispatcher().finished(this);
      }
    }
  }

上面比較重要的是這一句

  Response response = getResponseWithInterceptorChain();

如果重試和跟進攔截器沒有被取消的話,返回請求成功調用 responseCallback.onResponse蛹锰,如果中間有什么異常的話調用responseCallback.onFailure(RealCall.this, e);

getResponseWithInterceptorChain() 這個方法非常重要深胳,是整個OkHttp請求的核心,他是組裝了一系列的攔截鏈铜犬,進行鏈式調用舞终,最后返回組裝的請求結果

 /*一共五個攔截器  包括
  *   RetryAndFollowUpInterceptor 重試和跟進攔截器
  *   BridgeInterceptor           橋攔截器
  *   CacheInterceptor            緩存攔截器
  *   ConnectInterceptor          鏈接攔截器
  *   CallServerInterceptor       呼叫服務攔截器
  *
  *   RealInterceptorChain        實際攔截鏈
  * */
  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()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

接下來 我們來走進這些請求鏈的世界癣猾,去分析整個OkHttp請求的具體

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末敛劝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纷宇,更是在濱河造成了極大的恐慌夸盟,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,332評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件像捶,死亡現(xiàn)場離奇詭異上陕,居然都是意外死亡,警方通過查閱死者的電腦和手機作岖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評論 3 385
  • 文/潘曉璐 我一進店門唆垃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人痘儡,你說我怎么就攤上這事辕万。” “怎么了?”我有些...
    開封第一講書人閱讀 157,812評論 0 348
  • 文/不壞的土叔 我叫張陵渐尿,是天一觀的道長醉途。 經(jīng)常有香客問我,道長砖茸,這世上最難降的妖魔是什么隘擎? 我笑而不...
    開封第一講書人閱讀 56,607評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮凉夯,結果婚禮上货葬,老公的妹妹穿的比我還像新娘。我一直安慰自己劲够,他們只是感情好震桶,可當我...
    茶點故事閱讀 65,728評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著征绎,像睡著了一般蹲姐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上人柿,一...
    開封第一講書人閱讀 49,919評論 1 290
  • 那天柴墩,我揣著相機與錄音,去河邊找鬼凫岖。 笑死江咳,一個胖子當著我的面吹牛,可吹牛的內容都是我干的隘截。 我是一名探鬼主播扎阶,決...
    沈念sama閱讀 39,071評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼婶芭!你這毒婦竟也來了?” 一聲冷哼從身側響起着饥,我...
    開封第一講書人閱讀 37,802評論 0 268
  • 序言:老撾萬榮一對情侶失蹤犀农,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宰掉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呵哨,經(jīng)...
    沈念sama閱讀 44,256評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,576評論 2 327
  • 正文 我和宋清朗相戀三年轨奄,在試婚紗的時候發(fā)現(xiàn)自己被綠了孟害。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,712評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡挪拟,死狀恐怖挨务,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤谎柄,帶...
    沈念sama閱讀 34,389評論 4 332
  • 正文 年R本政府宣布丁侄,位于F島的核電站,受9級特大地震影響朝巫,放射性物質發(fā)生泄漏鸿摇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,032評論 3 316
  • 文/蒙蒙 一劈猿、第九天 我趴在偏房一處隱蔽的房頂上張望拙吉。 院中可真熱鬧,春花似錦揪荣、人聲如沸筷黔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽必逆。三九已至,卻和暖如春揽乱,著一層夾襖步出監(jiān)牢的瞬間名眉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評論 1 266
  • 我被黑心中介騙來泰國打工凰棉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留损拢,地道東北人。 一個月前我還...
    沈念sama閱讀 46,473評論 2 360
  • 正文 我出身青樓撒犀,卻偏偏與公主長得像福压,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子或舞,可洞房花燭夜當晚...
    茶點故事閱讀 43,606評論 2 350

推薦閱讀更多精彩內容

  • 這篇文章主要講 Android 網(wǎng)絡請求時所使用到的各個請求庫的關系荆姆,以及 OkHttp3 的介紹。(如理解有誤映凳,...
    小莊bb閱讀 1,150評論 0 4
  • 1.OkHttp源碼解析(一):OKHttp初階2 OkHttp源碼解析(二):OkHttp連接的"前戲"——HT...
    隔壁老李頭閱讀 15,680評論 36 72
  • OkHttp源碼的samples的簡單使用的示例: public static void main(String....
    _warren閱讀 739評論 0 1
  • OkHttp源碼分析-同步篇 很早就想拿okhttp開刀了胆筒,這次就記一次使用OKhttp的網(wǎng)絡請求。首先需要說明的...
    埃賽爾閱讀 976評論 1 2
  • 只是被今天的陽光和藍天吸引了诈豌,所以心情才那么好仆救,只是不想又被人誤解是不是戀愛了?唉矫渔,俗人俗事糾纏不清彤蔽!
    安一木閱讀 343評論 0 0