OkHttp源碼學(xué)習(xí)記錄(請求網(wǎng)絡(luò))

本篇文章通過源碼了解OkHttp的請求網(wǎng)絡(luò)流程,先來學(xué)習(xí)OkHttp的請求網(wǎng)絡(luò)流程尚揣。

OkHttp的請求網(wǎng)絡(luò)流程

1.從請求處理開始分析

當(dāng)我們要請求網(wǎng)絡(luò)時需要用OkHttp.newCall(request)進(jìn)行execute或者enqueue操作(同步和異步),當(dāng)調(diào)用newCall()方法時骇径,我們來看看發(fā)生了什么

newCall()方法

@Override public Call newCall(Request request) {
    return new RealCall(this, request);
  }

發(fā)現(xiàn)實際返回的是一個RealCall類炊林,我們調(diào)用enqueue()異步請求網(wǎng)絡(luò)實際上是調(diào)用了RealCall的enqueue()方法堵未,點進(jìn)去查看RealCall的enqueue方法做了什么

RealCall的enqueue()方法

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

可以看到最后是通過dispatcher來完成的摔蓝,接下來分析dispatcher


2. Dispatcher的任務(wù)調(diào)度

Dispatcher主要用于控制并發(fā)請求赂苗,主要維護(hù)了以下變量

//最大并發(fā)請求數(shù)
private int maxRequests = 64;

//每個主機(jī)最大請求數(shù)
private int MaxRequestsPerHost = 5;

//消費者線程池
private ExecutorService executorService;

//將要運行的異步請求隊列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

//正在運行的異步請求隊列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

//正在運行的同步請求隊列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

接下來看看Dispatcher的構(gòu)造方法

Dispatcher()構(gòu)造方法

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

Dispatcher有兩個構(gòu)造方法,可以使用自己設(shè)定的線程池贮尉。如果沒有設(shè)定線程池拌滋,則會在請求網(wǎng)絡(luò)前自己會創(chuàng)建默認(rèn)線程池。這個線程池類似于CachedThreadPool猜谚,比較適合執(zhí)行大量的耗時比較少的任務(wù)败砂。

另外上面已經(jīng)說過當(dāng)調(diào)用RealCall的enqueue()方法時,實際上調(diào)用了Dispatcher的enqueue()方法魏铅,那么我們就來看看Dispatcher的enqueue()方法做了什么

Dispatcher的enqueue()方法

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

當(dāng)正在運行的異步請求隊列中的數(shù)量小于64并且正在運行的請求主機(jī)數(shù)小于5時昌犹,把請求加載到runningAsyncCalls中并在線程池中執(zhí)行,否則就加入到readyAsyncCalls中進(jìn)行緩存等待

線程池中傳進(jìn)來的參數(shù)AsyncCall览芳,是RealCall的內(nèi)部類斜姥,其內(nèi)部也實現(xiàn)了execute()方法,下面我們看看execute()方法做了什么

execute()方法

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain(forWebSocket);
        if (canceled) {
          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!
          logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

這里只需要看finally里無論請求結(jié)果如何都會執(zhí)行finished()方法沧竟,下面看看finished()方法做了什么

finished()方法

synchronized void finished(AsyncCall call) {
    if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
    promoteCalls();
  }

finished()方法將此次請求從runningAsyncCalls用remove()移除后還調(diào)用了promoteCalls()方法铸敏,下面看看promoteCalls()方法做了什么

promoteCalls()方法

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

最關(guān)鍵的一點就是會從readyAsyncCalls中用迭代器取出下一個請求,變成AsyncCall的call對象后加入runningAsyncCalls中并交由線程池處理悟泵。

還記得剛才的AsyncCall的execute()方法嗎杈笔?在第一個try塊中,getResponseWithInterceptorChain()方法返回了Response糕非,明顯這是在請求網(wǎng)絡(luò)


3. Interceptor攔截器

接下來查看getResponseWithInterceptorChain()方法做了什么

getResponseWithInterceptorChain()方法

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

發(fā)現(xiàn)這個方法里new了一個ApplicationInterceptorChain蒙具,是一個攔截器鏈,這個類也是RealCall的內(nèi)部類朽肥,接下來它執(zhí)行了proceed()方法

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

在第一個if塊第2行使用get()方法從攔截器隊列里取出攔截器店量,如果當(dāng)前存在多個攔截器時會在下一行使用intercept()方法阻塞,等待下一個攔截器的調(diào)用返回

攔截器是一種能夠監(jiān)控鞠呈,重寫融师,重試調(diào)用的機(jī)制,通常情況下攔截器用來添加蚁吝、移除旱爆、轉(zhuǎn)換請求和響應(yīng)頭部信息。

查看上面代碼最后一個return語句窘茁,返回一個getResponse()表示沒有更多攔截器的話就執(zhí)行網(wǎng)絡(luò)請求怀伦,我們看看getResponse()方法做了什么

getResponse()方法

Response getResponse(Request request, boolean forWebSocket) throws IOException {
    // Copy body metadata to the appropriate request headers.
    RequestBody body = request.body();
    if (body != null) {
      Request.Builder requestBuilder = request.newBuilder();

      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }

      request = requestBuilder.build();
    }

    // Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
    engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);

    int followUpCount = 0;
    while (true) {
      if (canceled) {
        engine.releaseStreamAllocation();
        throw new IOException("Canceled");
      }

      boolean releaseConnection = true;
      try {
        engine.sendRequest();
        engine.readResponse();
        releaseConnection = false;
      } catch (RequestException e) {
        // The attempt to interpret the request failed. Give up.
        throw e.getCause();
      } catch (RouteException e) {
···
  }
}

這段代碼過長未截取完,以···表示山林,我們省略前半和后半部分房待,直接來到while循環(huán)處的上兩行,發(fā)現(xiàn)這里new了一個HttpEngine類并且在下面的try/catch塊中調(diào)用了sendRequest()方法和readResponse()方法,下面我們看看這兩個方法做了什么


4. 緩存策略

sendRequest()方法

public void sendRequest() throws RequestException, RouteException, IOException {
    if (cacheStrategy != null) return; // Already sent.
    if (httpStream != null) throw new IllegalStateException();

    Request request = networkRequest(userRequest);

    InternalCache responseCache = Internal.instance.internalCache(client);
    Response cacheCandidate = responseCache != null
        ? responseCache.get(request)
        : null;

    long now = System.currentTimeMillis();
    cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
    networkRequest = cacheStrategy.networkRequest;
    cacheResponse = cacheStrategy.cacheResponse;

    if (responseCache != null) {
      responseCache.trackResponse(cacheStrategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      userResponse = new Response.Builder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_BODY)
          .build();
      return;
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .cacheResponse(stripBody(cacheResponse))
          .build();
      userResponse = unzip(userResponse);
      return;
    }

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean success = false;
    try {
      httpStream = connect();
      httpStream.setHttpEngine(this);
···
  }
}  

這段代碼同樣很長桑孩,我們省略前半和后半部分拜鹤,瀏覽一遍發(fā)現(xiàn)代碼顯然是在發(fā)送請求,但是最主要的是做了緩存策略流椒。第2個if塊中的cacheCandidate是上次與服務(wù)器交互時緩存的Response敏簿,這里緩存均基于Map。

key是請求中url的md5宣虾,value是在文件中查詢到的緩存惯裕,頁面置換基于LRU算法读宙,我們現(xiàn)在只需要知道cacheCandidate是一個可讀取緩存Header的Response即可祸穷。

往下走第3個if塊的上面cacheStrategy進(jìn)行一些處理得到networkRequest和cacheResponse這兩個值,根據(jù)這兩個值的數(shù)據(jù)是否為null來進(jìn)行進(jìn)一步處理屋彪。

  1. 在networkRequest和cacheResponse都為null時鹉胖,即不進(jìn)行網(wǎng)絡(luò)請求并且緩存不存在或者過期握玛,此時返回504錯誤
  2. 當(dāng)networkRequest為null時也就是不進(jìn)行網(wǎng)絡(luò)請求,如果緩存可以使用時則直接返回緩存次员,其他情況則請求網(wǎng)絡(luò)

接下來看看HttpEngine的readResponse()方法

readResponse()方法

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (validate(cacheResponse, networkResponse)) {
        userResponse = cacheResponse.newBuilder()
            .request(userRequest)
            .priorResponse(stripBody(priorResponse))
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();
        releaseStreamAllocation();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        InternalCache responseCache = Internal.instance.internalCache(client);
        responseCache.trackConditionalCacheHit();
        responseCache.update(cacheResponse, stripBody(userResponse));
        userResponse = unzip(userResponse);
        return;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    userResponse = networkResponse.newBuilder()
        .request(userRequest)
        .priorResponse(stripBody(priorResponse))
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (hasBody(userResponse)) {
      maybeCache();
      userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
    }
  }

這個方法主要用來解析Http響應(yīng)報頭,如果有緩存并且可用王带,即第1個if語句和第2個if語句的判斷條件為真淑蔚,則用緩存數(shù)據(jù)并更新緩村,否則就用網(wǎng)絡(luò)請求返回的數(shù)據(jù)愕撰。接下來看看第2個if語句中validate()方法判斷緩存是否可用是如何工作的

validate()方法

private static boolean validate(Response cached, Response network) {
    if (network.code() == HTTP_NOT_MODIFIED) {
      return true;
    }

    // The HTTP spec says that if the network's response is older than our
    // cached response, we may return the cache's response. Like Chrome (but
    // unlike Firefox), this client prefers to return the newer response.
    Date lastModified = cached.headers().getDate("Last-Modified");
    if (lastModified != null) {
      Date networkLastModified = network.headers().getDate("Last-Modified");
      if (networkLastModified != null
          && networkLastModified.getTime() < lastModified.getTime()) {
        return true;
      }
    }

    return false;
  }

第一個if語句中如果緩存有效刹衫,則返回304 Not Modified,否則直接返回body搞挣。如果緩存過期或者強(qiáng)制放棄緩存带迟,則緩存策略全部交給服務(wù)器判斷,客戶端只需要發(fā)送條件GET請求即可囱桨。
條件GET請求有兩種方式仓犬,一種是Last-Modified-Date,另一種是ETag舍肠。這里采用的是前者搀继,通過緩存和網(wǎng)絡(luò)請求響應(yīng)中的Last-Modified來計算是否是最新數(shù)據(jù),如果是翠语,則緩存有效


5. 失敗重連

最后我們再回到RealCall類的getResponse()方法叽躯,把沒截完的代碼補(bǔ)全,如下

getResponse()方法

boolean releaseConnection = true;
      try {
        engine.sendRequest();
        engine.readResponse();
        releaseConnection = false;
      } catch (RequestException e) {
        // The attempt to interpret the request failed. Give up.
        throw e.getCause();
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null);
        if (retryEngine != null) {
          releaseConnection = false;
          engine = retryEngine;
          continue;
        }
        // Give up; recovery is not possible.
        throw e.getLastConnectException();
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        HttpEngine retryEngine = engine.recover(e, null);
        if (retryEngine != null) {
          releaseConnection = false;
          engine = retryEngine;
          continue;
        }

        // Give up; recovery is not possible.
        throw e;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          StreamAllocation streamAllocation = engine.close();
          streamAllocation.release();
        }
      }

      Response response = engine.getResponse();
      Request followUp = engine.followUpRequest();

      if (followUp == null) {
        if (!forWebSocket) {
          engine.releaseStreamAllocation();
        }
        return response;
      }

      StreamAllocation streamAllocation = engine.close();

      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (!engine.sameConnection(followUp.url())) {
        streamAllocation.release();
        streamAllocation = null;
      }

      request = followUp;
      engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
          response);
    }
  }

發(fā)現(xiàn)上述代碼在幾個catch塊中只要發(fā)生IOException或者RouteException就會調(diào)用engine的recover()方法肌括,接下來看看recover()方法做了什么

recover()方法

public HttpEngine recover(IOException e, Sink requestBodyOut) {
    if (!streamAllocation.recover(e, requestBodyOut)) {
      return null;
    }

    if (!client.retryOnConnectionFailure()) {
      return null;
    }

    StreamAllocation streamAllocation = close();

    // For failure recovery, use the same route selector with a new connection.
    return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
        forWebSocket, streamAllocation, (RetryableSink) requestBodyOut, priorResponse);
  }

觀察它的return語句点骑,返回了一個new HttpEngine并返回,用于完成重連,到這里就是一個完整的OkHttp請求流程黑滴,我們串聯(lián)一下調(diào)用的方法有哪些:

用于請求處理:
newCall()——RealCall的enqueue()

用于任務(wù)調(diào)度:
RealCall的enqueue()——Dispatcher的enqueue()——execute()——finished()——promoteCalls()

用于攔截器:
execute()——getResponseWithInterceptorChain()——proceed()——getResponse()

用于緩存:
getResponse()——sendRequest()
getResponse()——readResponse()——validate()

用于失敗重連:
getResponse()——recover()


具體流程如下


image.png

本文摘抄自《Android進(jìn)階之光——劉望舒》憨募,為自己學(xué)習(xí)路程中的記錄,不以盈利為目的跷跪。


歡迎指正馋嗜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市吵瞻,隨后出現(xiàn)的幾起案子葛菇,更是在濱河造成了極大的恐慌,老刑警劉巖橡羞,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眯停,死亡現(xiàn)場離奇詭異,居然都是意外死亡卿泽,警方通過查閱死者的電腦和手機(jī)莺债,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來签夭,“玉大人齐邦,你說我怎么就攤上這事〉谧猓” “怎么了措拇?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長慎宾。 經(jīng)常有香客問我丐吓,道長,這世上最難降的妖魔是什么趟据? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任券犁,我火速辦了婚禮,結(jié)果婚禮上汹碱,老公的妹妹穿的比我還像新娘粘衬。我一直安慰自己,他們只是感情好咳促,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布色难。 她就那樣靜靜地躺著,像睡著了一般等缀。 火紅的嫁衣襯著肌膚如雪枷莉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天尺迂,我揣著相機(jī)與錄音笤妙,去河邊找鬼冒掌。 笑死,一個胖子當(dāng)著我的面吹牛蹲盘,可吹牛的內(nèi)容都是我干的股毫。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼召衔,長吁一口氣:“原來是場噩夢啊……” “哼铃诬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起苍凛,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤趣席,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后醇蝴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宣肚,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年悠栓,在試婚紗的時候發(fā)現(xiàn)自己被綠了霉涨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡惭适,死狀恐怖笙瑟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情癞志,我是刑警寧澤往枷,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站今阳,受9級特大地震影響师溅,放射性物質(zhì)發(fā)生泄漏茅信。R本人自食惡果不足惜盾舌,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蘸鲸。 院中可真熱鬧妖谴,春花似錦、人聲如沸酌摇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窑多。三九已至仍稀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間埂息,已是汗流浹背技潘。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工遥巴, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人享幽。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓铲掐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親值桩。 傳聞我的和親對象是個殘疾皇子摆霉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351

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