okhttp的使用和及簡(jiǎn)單源碼分析

okhttp是目前非常常用的網(wǎng)絡(luò)請(qǐng)求框架,我們?cè)谑褂盟耐瑫r(shí)也要看他是怎么實(shí)現(xiàn)的,這篇文章我們簡(jiǎn)單分析以下它的請(qǐng)求流程砾淌。
參考并感謝:https://blog.csdn.net/json_it/article/details/78404010
https://blog.csdn.net/dingding_android/article/details/51942000
https://blog.csdn.net/mwq384807683/article/details/71173442?locationNum=8&fps=1
1稼钩、使用(同步請(qǐng)求)

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url("http://www.baidu.com")
              .build();
        Response response = client.newCall(request).execute();

首先我們new了一個(gè)OkHttpClient對(duì)象,這個(gè)類包含了我們網(wǎng)絡(luò)請(qǐng)求需要的協(xié)議苟呐,http版本協(xié)議痒芝,連接池,調(diào)度器等牵素。之后利用Builder模式創(chuàng)建了請(qǐng)求對(duì)象Request严衬,默認(rèn)的請(qǐng)求方式是get。最后通過newCall(request)創(chuàng)建了Call對(duì)象并執(zhí)行了該對(duì)象的excute方法笆呆。具體分析來看源碼:

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
   @Override 
   public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
   }

我們newCall方法實(shí)際是新建了一個(gè)RealCall對(duì)象瞳步,那么RealCall對(duì)象又是什么呢,RealCall實(shí)際上是實(shí)現(xiàn)了Call接口

public interface Call {
  Request request();
  Response execute() throws IOException;//同步請(qǐng)求腰奋,會(huì)在當(dāng)前線程執(zhí)行請(qǐng)求并阻塞當(dāng)前線程
  void enqueue(Callback responseCallback);//異步請(qǐng)求单起,后面會(huì)分析
  void cancel();
  boolean isCanceled();
  interface Factory {
    // 根據(jù)一個(gè)Http請(qǐng)求生成一個(gè)OKHttp請(qǐng)求。
    Call newCall(Request request);
  }
}

那么RealCall是怎么實(shí)現(xiàn)這些方法呢劣坊?

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

從代碼中可以看到嘀倒,首先判斷這個(gè)請(qǐng)求是否是第一次執(zhí)行,如果是局冰,則繼續(xù)向下執(zhí)行测蘑,否則會(huì)拋出一個(gè)異常,也就是說一個(gè)請(qǐng)求只能執(zhí)行一次康二。
之后碳胳,調(diào)用client.dispatcher().executed(this),即調(diào)用調(diào)度器的excuted方法沫勿,將當(dāng)前請(qǐng)求加入請(qǐng)求隊(duì)列中挨约。內(nèi)部具體的實(shí)現(xiàn)我們?cè)诤竺娈惒秸?qǐng)求中分析。
之后調(diào)用getResponseWithInterceptorChain()得到響應(yīng)Response产雹,并返回诫惭。同步請(qǐng)求分析到這里就結(jié)束了,重頭戲在后面——異步請(qǐng)求蔓挖。

2夕土、異步請(qǐng)求


client.newCall(request).enqueue(new Callback() {
         @Override
                public void onFailure(Call call, IOException e) {

                  }
        @Override
           public void onResponse(Call call, Response response) throws IOException {

                   }
});

這里我們使用RealCall的enqueue方法并傳入一個(gè)CallBack對(duì)象來進(jìn)行回調(diào)
我們來看下enqueue方法的源碼:

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

同樣的,異步請(qǐng)求首先也要判斷該請(qǐng)求是否是第一次執(zhí)行瘟判。
之后調(diào)用 client.dispatcher().enqueue(new AsyncCall(responseCallback))怨绣,我們的callback對(duì)象被當(dāng)作參數(shù)封裝進(jìn)了AsynCall對(duì)象角溃,我們來看下AsynCall對(duì)象的源碼

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;
    private final boolean forWebSocket;

    private AsyncCall(Callback responseCallback, boolean forWebSocket) {
      super("OkHttp %s", originalRequest.url().toString());
      this.responseCallback = responseCallback;
      this.forWebSocket = forWebSocket;
    }

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

    Request request() {
      return originalRequest;
    }

    Object tag() {
      return originalRequest.tag();
    }

    void cancel() {
      RealCall.this.cancel();
    }

    RealCall get() {
      return RealCall.this;
    }

    /**
      過一遍攔截器鏈,并執(zhí)行請(qǐng)求篮撑,然后調(diào)用回調(diào)函數(shù)开镣。
    */
    @Override protected void execute() {
      // 保證onFailure最多只會(huì)被調(diào)用一次
      boolean signalledCallback = false;
      try {
        // 進(jìn)入連接器鏈,并執(zhí)行請(qǐng)求
        Response response = getResponseWithInterceptorChain(forWebSocket);
        // 如果請(qǐng)求被取消咽扇,調(diào)用onFailure
        if (canceled) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          // 正常情況邪财,調(diào)用onResponse
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        // 如果上面有調(diào)用過回調(diào),就不調(diào)了质欲,這里保證onFailure只會(huì)被調(diào)用一次
        if (signalledCallback) {
          logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        // 運(yùn)行隊(duì)列移除請(qǐng)求
        client.dispatcher().finished(this);
      }
    }
  }

可以看到树埠,AsynCall實(shí)際上本身是個(gè)任務(wù),執(zhí)行時(shí)會(huì)調(diào)用execute方法嘶伟,在該方法中我們先調(diào)用Response response = getResponseWithInterceptorChain(forWebSocket)怎憋,通過責(zé)任鏈來獲取response,并執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。那么getResponseWithInterceptorChain()這個(gè)方法九昧,就是發(fā)送請(qǐng)求和獲取響應(yīng)的主要核心了绊袋,我們看看具體是怎么實(shí)現(xiàn)的:


Response getResponseWithInterceptorChain() throws IOException {
    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, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
 
    return chain.proceed(originalRequest);
}

這里面我們將自定義的攔截器和okhttp自身的攔截器加入至list中,并封裝成一個(gè)責(zé)任鏈對(duì)象Interceptor.Chain铸鹰,最后調(diào)用chain.proceed(request)來執(zhí)行我們的請(qǐng)求并獲得響應(yīng)癌别。那我們?cè)賮砜纯碿hain.proceed里干了些什么

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
 
    ......
 
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
 
    ......
 
    return response;
}

可以看到,proceed方法中判斷index(此時(shí)為0)是否大于或者等于client.interceptors(List )的大小蹋笼。由于httpStream為null展姐,所以首先創(chuàng)建next攔截器鏈,主需要把索引置為index+1即可剖毯;然后獲取第一個(gè)攔截器圾笨,調(diào)用其intercept方法。在intercept方法中我們?cè)僖淮握{(diào)用了chain.proceed的方法去調(diào)用下一個(gè)攔截器的intercept方法逊谋。這樣我們的request請(qǐng)求層層向下傳遞擂达,而最后一個(gè)攔截器處理完畢時(shí),返回的response又層層向上返回胶滋,最后得到的就是最終經(jīng)過加工后的響應(yīng)板鬓。

那么這些攔截器有那些呢?
1)在配置 OkHttpClient 時(shí)設(shè)置的 interceptors镀钓;
2)負(fù)責(zé)失敗重試以及重定向的 RetryAndFollowUpInterceptor穗熬;
3)負(fù)責(zé)把用戶構(gòu)造的請(qǐng)求轉(zhuǎn)換為發(fā)送到服務(wù)器的請(qǐng)求镀迂、把服務(wù)器返回的響應(yīng)轉(zhuǎn)換為用戶友好的響應(yīng)的 BridgeInterceptor丁溅;
4)負(fù)責(zé)讀取緩存直接返回、更新緩存的 CacheInterceptor探遵;
5)負(fù)責(zé)和服務(wù)器建立連接的 ConnectInterceptor窟赏;
6)配置 OkHttpClient 時(shí)設(shè)置的 networkInterceptors妓柜;
7)負(fù)責(zé)向服務(wù)器發(fā)送請(qǐng)求數(shù)據(jù)、從服務(wù)器讀取響應(yīng)數(shù)據(jù)的 CallServerInterceptor涯穷。
請(qǐng)求流程分析到這里棍掐,我們知道我們的請(qǐng)求都是通過調(diào)度器將請(qǐng)求加入請(qǐng)求隊(duì)列中,所以我們最后再來看一下dispatch是如何工作的拷况。

public final class Dispatcher {
  /** 最大并發(fā)請(qǐng)求數(shù)為64 */
  private int maxRequests = 64;
  /** 每個(gè)主機(jī)最大請(qǐng)求數(shù)為5 */
  private int maxRequestsPerHost = 5;

  /** 線程池 */
  private ExecutorService executorService;

  /** 準(zhǔn)備執(zhí)行的請(qǐng)求 */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** 正在執(zhí)行的異步請(qǐng)求作煌,包含已經(jīng)取消但未執(zhí)行完的請(qǐng)求 */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** 正在執(zhí)行的同步請(qǐng)求,包含已經(jīng)取消單未執(zhí)行完的請(qǐng)求 */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

再調(diào)度器中赚瘦,創(chuàng)建了一個(gè)線程池

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í)創(chuàng)建更多線程,阻塞隊(duì)列只保留一個(gè)任務(wù)起意,當(dāng)該任務(wù)被執(zhí)行鹰服,下一個(gè)任務(wù)才能進(jìn)入隊(duì)列。線程的存活時(shí)間為60秒揽咕,60秒還沒有任務(wù)需要執(zhí)行則線程銷毀悲酷。
之后我們來看enqueue方法

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

可以看到如果正在執(zhí)行的請(qǐng)求總數(shù)<=64 && 單個(gè)Host正在執(zhí)行的請(qǐng)求<=5,則將請(qǐng)求加入到runningAsyncCalls集合中亲善,緊接著就是利用線程池執(zhí)行該請(qǐng)求设易,否則就將該請(qǐng)求放入readyAsyncCalls集合中。上面我們已經(jīng)說了蛹头,AsyncCall是Runnable的子類(間接)亡嫌,因此,在線程池中最終會(huì)調(diào)用AsyncCall的execute()方法執(zhí)行異步請(qǐng)求掘而。

這就是okhttp的工作流程挟冠,就分析到這里。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末袍睡,一起剝皮案震驚了整個(gè)濱河市知染,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌斑胜,老刑警劉巖控淡,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異止潘,居然都是意外死亡掺炭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門凭戴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涧狮,“玉大人,你說我怎么就攤上這事≌咴” “怎么了肤视?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)涉枫。 經(jīng)常有香客問我邢滑,道長(zhǎng),這世上最難降的妖魔是什么愿汰? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任困后,我火速辦了婚禮,結(jié)果婚禮上衬廷,老公的妹妹穿的比我還像新娘操灿。我一直安慰自己,他們只是感情好泵督,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布趾盐。 她就那樣靜靜地躺著,像睡著了一般小腊。 火紅的嫁衣襯著肌膚如雪救鲤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天秩冈,我揣著相機(jī)與錄音本缠,去河邊找鬼。 笑死入问,一個(gè)胖子當(dāng)著我的面吹牛丹锹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芬失,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼楣黍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了棱烂?” 一聲冷哼從身側(cè)響起租漂,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颊糜,沒想到半個(gè)月后哩治,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡衬鱼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年业筏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸟赫。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蒜胖,死狀恐怖消别,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情翠勉,我是刑警寧澤妖啥,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布霉颠,位于F島的核電站对碌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蒿偎。R本人自食惡果不足惜朽们,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诉位。 院中可真熱鬧骑脱,春花似錦、人聲如沸苍糠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岳瞭。三九已至拥娄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瞳筏,已是汗流浹背稚瘾。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留姚炕,地道東北人摊欠。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像柱宦,于是被迫代替她去往敵國和親些椒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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

  • 前言 用OkHttp很久了掸刊,也看了很多人寫的源碼分析摊沉,在這里結(jié)合自己的感悟,記錄一下對(duì)OkHttp源碼理解的幾點(diǎn)心...
    Java小鋪閱讀 1,518評(píng)論 0 13
  • OkHttp源碼分析 在現(xiàn)在的Android開發(fā)中痒给,請(qǐng)求網(wǎng)絡(luò)獲取數(shù)據(jù)基本上成了我們的標(biāo)配说墨。在早期的Android開...
    BlackFlag閱讀 326評(píng)論 0 5
  • 基本使用 GET請(qǐng)求 POST 請(qǐng)求 內(nèi)部請(qǐng)求分析 val client = OkHttpClient() 通過O...
    行走的老者閱讀 240評(píng)論 0 0
  • 主目錄見:Android高級(jí)進(jìn)階知識(shí)(這是總目錄索引)?OkHttp的知識(shí)點(diǎn)實(shí)在是不少,優(yōu)秀的思想也有很多苍柏,這里只...
    ZJ_Rocky閱讀 2,293評(píng)論 2 6
  • 站在塵世的山峰 我永遠(yuǎn)活不過一只鷹的高度 馬匹被風(fēng)牽走尼斧,遠(yuǎn)方在睫毛下 游離,一些叫做英雄的生物 在云卷云舒里生生滅...
    憂傷沒有傷口閱讀 303評(píng)論 0 0