OkHttp源碼深入

在OkHttp3中腰根,其靈活性很大程度上體現(xiàn)在可以攔截 其任意一個(gè)環(huán)節(jié)激才,而這個(gè)優(yōu)勢(shì)便是okhttp3整個(gè)請(qǐng)求響應(yīng)架構(gòu)體系的精髓所在,先放出一張主框架請(qǐng)求流程圖额嘿,接著再分析源碼


OKHttp簡(jiǎn)單用例

String url = "http://wwww.baidu.com";
OkHttpClient okHttpClient = new OkHttpClient();
final 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) {
        Log.d(TAG, "onFailure: ");
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.d(TAG, "onResponse: " + response.body().string());
    }
});

首先我們來(lái)看OkHttpClient的部分源碼

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

Builder是其內(nèi)部靜態(tài)類(lèi)瘸恼,它的構(gòu)造方法如下

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

我們?cè)賮?lái)看newCall方法中發(fā)生了什么

  /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }
  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;
  }

我們調(diào)用Call的enqueue、execute其實(shí)都是最終調(diào)用RealCall 的enqueue册养、execute东帅,下面我們來(lái)看它的源碼實(shí)現(xiàn)

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

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

這里可以看到最后都會(huì)調(diào)用OkHttpClient實(shí)例中初始化的Dispatcher實(shí)例里的enqueue、executed捕儒。下面我們著重說(shuō)明Dispatcher是什么以及有什么作用

Dispatcher

Dispatcher是一個(gè)任務(wù)調(diào)度器冰啃,它的主要作用有如下幾點(diǎn):

  • 控制異步請(qǐng)求最大并發(fā)數(shù)和同一主機(jī)的請(qǐng)求連接數(shù)
  • 執(zhí)行同步和異步請(qǐng)求的分發(fā)
  • 執(zhí)行同步和異步請(qǐng)求的結(jié)束處理

Dispatcher部分源碼如下:

public final class Dispatcher {
  //請(qǐng)求默認(rèn)最大并發(fā)數(shù)
  private int maxRequests = 64;
  //同一主機(jī)最大連接數(shù)
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  /** 異步請(qǐng)求線程池 */
  private @Nullable ExecutorService executorService;

  /** 異步請(qǐng)求準(zhǔn)備隊(duì)列 */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** 異步請(qǐng)求運(yùn)行時(shí)隊(duì)列 */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** 同步請(qǐng)求運(yùn)行隊(duì)列*/
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

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

  public Dispatcher() {
  }

  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;
  }
  .....
  .....
}
控制異步請(qǐng)求最大并發(fā)數(shù)和同一主機(jī)的請(qǐng)求連接數(shù)

從上面RealCall 的異步請(qǐng)求源碼可以看到請(qǐng)求時(shí),實(shí)例化了一個(gè)AsyncCall

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

AsyncCall是RealCall的一個(gè)內(nèi)部類(lèi)刘莹,其源碼如下

  final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

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

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    /**
     * Attempt to enqueue this async call on {@code executorService}. This will attempt to clean up
     * if the executor has been shut down by reporting the call as failed.
     */
    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!
        }
      }
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      timeout.enter();
      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) {
        e = timeoutExit(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);
      }
    }
  }

NamedRunnable 這里不做過(guò)多的介紹阎毅,你只需要只要這是一個(gè)實(shí)現(xiàn)了Runnable接口的抽象類(lèi)即可。

下面我們來(lái)看Dispatcher中enqueue方法源碼

  void enqueue(AsyncCall call) {
    synchronized (this) {
     //加入準(zhǔn)備隊(duì)列
      readyAsyncCalls.add(call);
    }
    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();
        //是否超過(guò)最大并發(fā)數(shù)
        if (runningAsyncCalls.size() >= maxRequests) break;
        // 判斷同一主機(jī)是否大于的指定最大連接數(shù)
        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; 
        //如果滿(mǎn)足執(zhí)行條件点弯,從準(zhǔn)備隊(duì)列中移除
        i.remove();
        //添加到異步請(qǐng)求運(yùn)行隊(duì)列中
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }
      //判斷運(yùn)行隊(duì)列中是否還有任務(wù)
      isRunning = runningCallsCount() > 0;
    }
    
    //遍歷異步請(qǐng)求隊(duì)列扇调,加入異步請(qǐng)求線程池中運(yùn)行
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

到了這邊就可以看到了Dispatcher的第一個(gè)功能,控制異步請(qǐng)求最大并發(fā)數(shù)和同一主機(jī)的請(qǐng)求連接數(shù)

執(zhí)行異步請(qǐng)求和同步請(qǐng)求的分發(fā)

從上面異步請(qǐng)求的源碼可以分析出Dispatcher異步請(qǐng)求分發(fā)的流程抢肛,如下:

  • RealCall 的enqueue方法中生成一個(gè)AsyncCall異步任務(wù)
  • Dispatcher執(zhí)行enqueue方法把AsyncCall異步任務(wù)加入到準(zhǔn)備隊(duì)列中
  • 循環(huán)準(zhǔn)備隊(duì)列狼钮,判斷異步請(qǐng)求運(yùn)行隊(duì)列是否大于最大并發(fā)數(shù),大于則直接終止流程捡絮,等待異步請(qǐng)求運(yùn)行隊(duì)列空閑時(shí)再次觸發(fā)執(zhí)行熬芜。
  • 小于最大并發(fā)數(shù),則判斷異步請(qǐng)求運(yùn)行隊(duì)列同一主機(jī)的連接數(shù)是否大于預(yù)設(shè)值福稳,大于跳過(guò)此次請(qǐng)求任務(wù)涎拉,繼續(xù)從準(zhǔn)備隊(duì)列中循環(huán)下一個(gè)請(qǐng)求任務(wù)。
  • 異步請(qǐng)求運(yùn)行隊(duì)列同一主機(jī)的連接數(shù)小于預(yù)設(shè)值,則把AsyncCall異步任務(wù)從準(zhǔn)備隊(duì)列中刪除鼓拧,并加入異步請(qǐng)求運(yùn)行隊(duì)列中等待執(zhí)行

下面我們?cè)賮?lái)深入RealCall 中同步請(qǐng)求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);
    }
  }
  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

可以看到這邊則非常簡(jiǎn)單半火,直接把同步任務(wù)加入同步請(qǐng)求隊(duì)列runningSyncCalls中

執(zhí)行同步和異步請(qǐng)求的結(jié)束處理

從上訴promoteAndExecute方法中可以看到異步任務(wù)都會(huì)走AsyncCall 的executeOn方法,而executeOn最終會(huì)執(zhí)行到自身的execute方法季俩,而無(wú)論execute執(zhí)行成功與否都會(huì)執(zhí)行Dispatcher的finished钮糖。

從上面RealCall 中的同步方法可以看到最后都會(huì)執(zhí)行Dispatcher的finished。那么我們就有必要對(duì)這個(gè)方法進(jìn)行進(jìn)一步的剖析

  /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call);
  }

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

可以看到Dispatcher的finished方法中最后又會(huì)調(diào)到promoteAndExecute酌住,前面分析了Dispatcher異步請(qǐng)求流程店归,如果循環(huán)異步請(qǐng)求準(zhǔn)備隊(duì)列,異步請(qǐng)求循環(huán)隊(duì)列大于最大并發(fā)數(shù)或者同一主機(jī)的連接數(shù)大于預(yù)設(shè)值赂韵。則放棄處理娱节,直至異步請(qǐng)求運(yùn)行隊(duì)列空閑時(shí)再次觸發(fā)。而怎么觸發(fā)祭示,就是在這邊觸發(fā)的。

同時(shí)這個(gè)函數(shù)還會(huì)返回一個(gè)結(jié)果谴古,告知是否有請(qǐng)求任務(wù)正在運(yùn)行质涛。判斷是否有運(yùn)行中隊(duì)列通過(guò)以下方法實(shí)現(xiàn)

  public synchronized int runningCallsCount() {
    //返回同步請(qǐng)求隊(duì)列和異步請(qǐng)求隊(duì)列總和
    return runningAsyncCalls.size() + runningSyncCalls.size();
  }

如果沒(méi)有任務(wù)正在運(yùn)行,并且設(shè)置了idleCallback回調(diào)掰担,則會(huì)觸發(fā)idleCallback回調(diào)汇陆,觸發(fā)后續(xù)的操作。

OkHttp框架的攔截方法

就如文章一開(kāi)頭那張總圖概括带饱,無(wú)論是同步請(qǐng)求或者異步請(qǐng)求毡代,通過(guò)上面的源碼分析,可以發(fā)現(xiàn)最后都會(huì)走一個(gè)叫g(shù)etResponseWithInterceptorChain的方法勺疼。下面我們來(lái)看看這個(gè)方法的實(shí)現(xiàn)

  Response getResponseWithInterceptorChain() throws IOException {
    // 攔截器鏈
    List<Interceptor> interceptors = new ArrayList<>();
    //用戶(hù)自定義的攔截器
    interceptors.addAll(client.interceptors());
    //報(bào)錯(cuò)重試教寂、重定向攔截
    interceptors.add(retryAndFollowUpInterceptor);
    //橋接攔截器,橋接應(yīng)用層與網(wǎng)絡(luò)層执庐,添加必要的頭
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //緩存攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //連接攔截器
    interceptors.add(new ConnectInterceptor(client));
    //從這就知道酪耕,通過(guò)okHttpClient.Builder#addNetworkInterceptor()傳進(jìn)來(lái)的攔截器只對(duì)非網(wǎng)頁(yè)的請(qǐng)求生效
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //請(qǐng)求攔截器,這里執(zhí)行真正的網(wǎng)絡(luò)請(qǐng)求
    interceptors.add(new CallServerInterceptor(forWebSocket));

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

    return chain.proceed(originalRequest);
  }

這里不做具體攔截器的源碼分析轨淌,感興趣的同學(xué)自己去深入

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末迂烁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子递鹉,更是在濱河造成了極大的恐慌盟步,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躏结,死亡現(xiàn)場(chǎng)離奇詭異却盘,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)谷炸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)北专,“玉大人,你說(shuō)我怎么就攤上這事旬陡⊥赝牵” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵描孟,是天一觀的道長(zhǎng)驶睦。 經(jīng)常有香客問(wèn)我,道長(zhǎng)匿醒,這世上最難降的妖魔是什么场航? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮廉羔,結(jié)果婚禮上溉痢,老公的妹妹穿的比我還像新娘。我一直安慰自己憋他,他們只是感情好孩饼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著竹挡,像睡著了一般镀娶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上揪罕,一...
    開(kāi)封第一講書(shū)人閱讀 51,488評(píng)論 1 302
  • 那天梯码,我揣著相機(jī)與錄音,去河邊找鬼好啰。 笑死轩娶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的坎怪。 我是一名探鬼主播罢坝,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼搅窿!你這毒婦竟也來(lái)了嘁酿?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤男应,失蹤者是張志新(化名)和其女友劉穎闹司,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體沐飘,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡游桩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年牲迫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片借卧。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盹憎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铐刘,到底是詐尸還是另有隱情陪每,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布镰吵,位于F島的核電站檩禾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一炕横、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洽沟,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至续挟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侥衬,已是汗流浹背诗祸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留轴总,地道東北人直颅。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像怀樟,于是被迫代替她去往敵國(guó)和親功偿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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

  • 簡(jiǎn)介 目前在HTTP協(xié)議請(qǐng)求庫(kù)中往堡,OKHttp應(yīng)當(dāng)是非承岛桑火的,使用也非常的簡(jiǎn)單虑灰。網(wǎng)上有很多文章寫(xiě)了關(guān)于OkHttp...
    第八區(qū)閱讀 1,380評(píng)論 1 5
  • 在日常開(kāi)發(fā)中網(wǎng)絡(luò)請(qǐng)求是很常見(jiàn)的功能吨瞎。OkHttp作為Android開(kāi)發(fā)中最常用的網(wǎng)絡(luò)請(qǐng)求框架,在Android開(kāi)發(fā)...
    maoqitian閱讀 1,177評(píng)論 1 11
  • 前言 用OkHttp很久了穆咐,也看了很多人寫(xiě)的源碼分析颤诀,在這里結(jié)合自己的感悟字旭,記錄一下對(duì)OkHttp源碼理解的幾點(diǎn)心...
    Java小鋪閱讀 1,518評(píng)論 0 13
  • 一、前言 在《OkHttp深入分析——基礎(chǔ)認(rèn)知部分》對(duì) OkHttp 源碼的工程已經(jīng)有一個(gè)大致的了解了崖叫,這篇文章里...
    仰簡(jiǎn)閱讀 1,037評(píng)論 0 4
  • 聲明:原創(chuàng)作品遗淳,轉(zhuǎn)載請(qǐng)注明出處http://www.reibang.com/p/6a1d4773a130[htt...
    蛇發(fā)女妖閱讀 1,198評(píng)論 0 7