OkHttp

  1. OkHttp 使用菊匿。
  • 創(chuàng)建OkHttpClient 對(duì)象狭莱。
  • build Request 對(duì)象奥裸,注入Call對(duì)象,new 出Call對(duì)象证杭。
  • 調(diào)用execute(同步)或者 enqueue(異步)發(fā)送請(qǐng)求
package com.example.myapplication;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class TestOkHttp {
   static OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .readTimeout(5, TimeUnit.MINUTES).build();

   //同步
    public static void synRequest(){
        Request request = new Request.Builder().url("http://www.baidu.com").get().build();
        Call call = okHttpClient.newCall(request);
        try {
            Response response = call.execute();
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //異步
    public static void AsynRequest(){
        Request request = new Request.Builder().url("http://www.baidu.com").get().build();
        Call call = okHttpClient.newCall(request);
        Callback callback = new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println(response.body().string());
            }
        };
        call.enqueue(callback);
    }
    public static void main(String[] args) {
        synRequest();
        AsynRequest();
    }
}

簡(jiǎn)圖
  1. OkHttp源碼分析田度。
image.png
  • Dispatcher 分發(fā)器
    用于維護(hù)請(qǐng)求的狀態(tài),并維護(hù)一個(gè)線程池解愤,執(zhí)行請(qǐng)求镇饺。
  /** Executes calls. Created lazily. */
  /** 線程池 */
  private @Nullable ExecutorService executorService;

  /** Ready async calls in the order they'll be run. */
  /** 準(zhǔn)備就緒的請(qǐng)求隊(duì)列 */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  /** (異步)正在執(zhí)行的請(qǐng)求隊(duì)列 ,并包含了已經(jīng)執(zhí)行送讲,被取消的請(qǐng)求*/
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  /** (同步)正在執(zhí)行的請(qǐng)求隊(duì)列 奸笤,并包含了已經(jīng)執(zhí)行,被取消的請(qǐng)求*/
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  • 異步請(qǐng)求任務(wù)調(diào)度
private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
//遍歷就緒請(qǐng)求隊(duì)列
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();
//判斷不能大于請(qǐng)求最大值
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
//判斷執(zhí)行線程不行超過最大線程數(shù)
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
//從就緒隊(duì)列移除
        i.remove();
//請(qǐng)求線程計(jì)數(shù)+1
        asyncCall.callsPerHost().incrementAndGet();
//添加到線程池執(zhí)行隊(duì)列
        executableCalls.add(asyncCall);
//添加到正在執(zhí)行的異步隊(duì)列
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }
//遍歷執(zhí)行請(qǐng)求
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }
  • interceptor 攔截器
image.png

image.png

執(zhí)行請(qǐng)求最主要的是攔截器鏈李茫,通過\color{red}{chain.proceed}和攔截器方法\color{red}{interceptor.intercept(next)}形成遞歸調(diào)用(chain.proceed這個(gè)被攔截器調(diào)用的代碼我就不貼了)

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    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, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

 public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.exchange != null && !this.exchange.connection().supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.exchange != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

\color{red}{總結(jié)3點(diǎn):}
\color{red}{1. 在發(fā)起請(qǐng)求前對(duì)響應(yīng)做處理揭保。}
\color{red}{2. 調(diào)用下個(gè)攔截器返回response。}
\color{red}{3. 對(duì)response處理返回給上一個(gè)攔截器魄宏。}

\color{red}{攔截器做什么秸侣?}

  1. RetryAndFollowUpInterceptor.png
  2. BridgeInterceptor.png
  3. 緩存攔截器,主要是采用\color{red}{DiskLRUCache},用get方法味榛,key的md5椭坚。
    緩存策略:
  • 緩存中沒有Response、不使用緩存搏色、https沒有成功握手善茎,hasConditions、!isCacheable频轿;cacheResponse=null垂涯,重新請(qǐng)求。
  • 其余的符合條件在head中打上標(biāo)記返回緩存航邢。
  1. ConnectInterceptor.png
  2. ConnectInterceptor.png

6.根據(jù)\color{red}{streamallocation}的數(shù)量啟動(dòng)清理連接池耕赘,采用的GC的標(biāo)記-清除算法--\color{red}{pruneAndGetAllocationCount}

image.png

  1. \color{red}{CallServerInterceptor}主要看下面代碼\color{red}{exchange}執(zhí)行請(qǐng)求
    @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();

    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        exchange.flushRequest();
        responseHeadersStarted = true;
        exchange.responseHeadersStart();
        responseBuilder = exchange.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        if (request.body().isDuplex()) {
          // Prepare a duplex body so that the application can send a request body later.
          exchange.flushRequest();
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, true));
          request.body().writeTo(bufferedRequestBody);
        } else {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, false));
          request.body().writeTo(bufferedRequestBody);
          bufferedRequestBody.close();
        }
      } else {
        exchange.noRequestBody();
        if (!exchange.connection().isMultiplexed()) {
          // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
          // from being reused. Otherwise we're still obligated to transmit the request body to
          // leave the connection in a consistent state.
          exchange.noNewExchangesOnConnection();
        }
      }
    } else {
      exchange.noRequestBody();
    }

    if (request.body() == null || !request.body().isDuplex()) {
      exchange.finishRequest();
    }

    if (!responseHeadersStarted) {
      exchange.responseHeadersStart();
    }

    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (code == 100) {
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      response = exchange.readResponseHeaders(false)
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

      code = response.code();
    }

    exchange.responseHeadersEnd(response);

    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      exchange.noNewExchangesOnConnection();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市膳殷,隨后出現(xiàn)的幾起案子操骡,更是在濱河造成了極大的恐慌,老刑警劉巖赚窃,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件册招,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡勒极,警方通過查閱死者的電腦和手機(jī)是掰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來河质,“玉大人冀惭,你說我怎么就攤上這事∠贫欤” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵媒楼,是天一觀的道長(zhǎng)乐尊。 經(jīng)常有香客問我,道長(zhǎng)划址,這世上最難降的妖魔是什么扔嵌? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮夺颤,結(jié)果婚禮上痢缎,老公的妹妹穿的比我還像新娘。我一直安慰自己世澜,他們只是感情好独旷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般嵌洼。 火紅的嫁衣襯著肌膚如雪案疲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天麻养,我揣著相機(jī)與錄音褐啡,去河邊找鬼。 笑死鳖昌,一個(gè)胖子當(dāng)著我的面吹牛备畦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播许昨,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼萍恕,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了车要?” 一聲冷哼從身側(cè)響起允粤,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎翼岁,沒想到半個(gè)月后类垫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琅坡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年悉患,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片榆俺。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡售躁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茴晋,到底是詐尸還是另有隱情陪捷,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布诺擅,位于F島的核電站市袖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏烁涌。R本人自食惡果不足惜苍碟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望撮执。 院中可真熱鬧微峰,春花似錦、人聲如沸抒钱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至症杏,卻和暖如春装获,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背厉颤。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工穴豫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逼友。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓精肃,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親帜乞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子司抱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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