OkHttp3 源碼分析(一)------簡單流程

看了有一段時間的OkHttp3的源碼了宁舰。今天動筆開始寫一寫掌实,本篇文章只是簡單的寫一下OkHttp3的一個過程盖高。(以后的文章會對OkHttp3的內(nèi)部進行分析)矢否。

OkHttp3 優(yōu)點:

1.支持http1/http2
2.對一臺機器的所有請求共享同一個socket
3.內(nèi)部有連接池仲闽,減少創(chuàng)建和鏈接時過多的時間消耗

設(shè)計模式

整個OkHttp用到很多設(shè)計模式:

1.外觀模式:

OKHttpClient 里面封裝了很多的類對象。其實就是將OKHttp的很多功能模塊僵朗,全部封裝到這個類中赖欣,讓這個類單獨提供對外的API。

 外觀模式(Facade Pattern)隱藏系統(tǒng)的復(fù)雜性验庙,并向客戶端提供了一個客戶端可以訪問系統(tǒng)的接口顶吮。這種類型的設(shè)計模式屬于結(jié)構(gòu)型模式,它向現(xiàn)有的系統(tǒng)添加一個接口粪薛,來隱藏系統(tǒng)的復(fù)雜性悴了。
這種模式涉及到一個單一的類,該類提供了客戶端請求的簡化方法和對現(xiàn)有系統(tǒng)類方法的委托調(diào)用违寿。

2.建造者模式

正因為內(nèi)部功能塊比較多湃交,大量使用了建造者模式,比如Reuqest的創(chuàng)建,等等吧藤巢。

建造者模式(Builder Pattern)使用多個簡單的對象一步一步構(gòu)建成一個復(fù)雜的對象搞莺。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式掂咒。
一個 Builder 類會一步一步構(gòu)造最終的對象才沧。該 Builder 類是獨立于其他對象的迈喉。

主體架構(gòu)和大概流程

IMG_20180129_093053.png

OkHttp的主要使用就是:
new OkHttpClient().newCall(request).execute();(同步);
new OkHttpClient().newCall(request).enqueue();(異步)
通過這行代碼温圆,我們可以捋出OkHttp的大致流程:

execute同步請求的方法:

newCall(request)的方法是返回一個RealCall

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


  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
//是用來跟蹤調(diào)用棧的信息的挨摸,不用深究
    captureCallStackTrace();
    try {
A://此方法只是把請求加入隊列并沒有真正執(zhí)行;
      client.dispatcher().executed(this);
B://真正執(zhí)行請求進行網(wǎng)絡(luò)請求返回結(jié)果
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

A:此處調(diào)用了Dispatcher的executed的方法 把Call加入到隊列中runningSyncCalls.add(call);(稍后分析Dispatcher)

B:調(diào)用攔截器返回結(jié)果捌木;

enqueue異步請求的方法:

調(diào)用RealCall的enqueue方法

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
A:
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

A:調(diào)用了Dispatcher的enqueue方法油坝。可以看到此方法參數(shù)中創(chuàng)建了一個AsyncCall(構(gòu)建call對象)刨裆。
其中Dispatcher的enqueue的方法


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

可以看出此段代碼也是把請求加入隊列澈圈,然后執(zhí)行 executorService().execute(call),其中AsyncCall 繼承NamedRunnable 是一個線程帆啃,我們應(yīng)該看他的execute()方法瞬女,此方法中也同樣調(diào)用了 Response response = getResponseWithInterceptorChain();方法。
executorService屬于線程池努潘,所以此方法executorService().execute(call)執(zhí)行的是AsyncCall方法的execute方法诽偷;(稍后會對Dispatcher進行分析)

Dispatcher(任務(wù)分發(fā)器)

我們知道OkHttp內(nèi)部是有一個線程池的,這個線程池就在Dispatcher中疯坤,其實這個類就是一個任務(wù)隊列报慕。
那么我們來看一下Dispatcher的成員變量:

//最大并發(fā)請求數(shù)為64
  private int maxRequests = 64;
//每個主機最大請求數(shù)是5
  private int maxRequestsPerHost = 5;
//線程
  private @Nullable Runnable idleCallback;
//線程池
  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;
//準備執(zhí)行的異步請求隊列,對象是異步請求
  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//正在執(zhí)行的異步請求隊列压怠,其中包括了已經(jīng)取消了但是還未執(zhí)行完的請求
  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//正在執(zhí)行的同步請求隊列眠冈,同樣包括了已經(jīng)取消了但是還未執(zhí)行完的請求
  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

看完成員變量我們發(fā)現(xiàn)其中有兩個異步隊列,這是為什么菌瘫?

采用Deque作為緩存蜗顽,按照入隊的順序先進先出,Deque雙端隊列雨让,繼承自Queue雇盖,我們通過這連個隊列我們不難看出是采用了生產(chǎn)消費者模式,結(jié)合線程池實現(xiàn)了低阻塞的運行栖忠。在大多數(shù)時候崔挖,每個緩存它們都只是訪問自己的雙端隊列,這樣的話極大地減少了競爭娃闲。當(dāng)工作者線程需要訪問另一個隊列時虚汛,它會從隊列的尾部而不是頭部獲取工作,因此進一步降低了隊列上的競爭程度皇帮。

Dispatcher的線程池ExecutorService


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

使用單利的方式創(chuàng)建線程池,那么我們來解釋一下線程池的幾個參數(shù)
1.int corePoolSize: 0 線程池的基本大小蛋辈,即在沒有任務(wù)需要執(zhí)行的時候線程池的大小属拾,并且只有在工作隊列滿了的情況下才會創(chuàng)建超出這個數(shù)量的線程将谊。
2.int maxmumPoolSize: Integer.MAX_VALUE 最大線程數(shù),就是當(dāng)前任務(wù)進行時渐白,此線程池能擴充的最大值尊浓。這里是無限大。
3.long keepAliveTime:60 當(dāng)前線程數(shù)大于核心線程數(shù)纯衍,成為空閑線程,當(dāng)空閑線程存活時間大于這個時間就會被取消
4.TimeUnit unit: TimeUnit.SECONDS 存活時間的單位是秒
5.BlockingQueue<Runnable> workQueue: new SynchronousQueue<Runnable>() 一個阻塞隊列,用來存儲等待執(zhí)行的任務(wù)
6.ThreadFactory threadFactory: 創(chuàng)建線程的工廠 Util.threadFactory("OkHttp Dispatcher", false)

前面我們分析了同步和一部的方法俺祠,就不做過多解釋了困鸥。

我們來看一下如何從ready到runing的添加的。
每次Call結(jié)束的時候都會調(diào)用finshed
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
          if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
          //每次remove完后歌亲,執(zhí)行promoteCalls來輪轉(zhuǎn)菇用。
          if (promoteCalls) promoteCalls();
          runningCallsCount = runningCallsCount();
          idleCallback = this.idleCallback;
        }
        //線程池為空時,執(zhí)行回調(diào)
        if (runningCallsCount == 0 && idleCallback != null) {
          idleCallback.run();
        }
      }
這個方法可以看出來是遍歷了readyAsyncCalls陷揪,把Call一一添加到了RunningAysncCalls惋鸥。
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; 
        }
   }

getResponseWithInterceptorChain()

這一步是OkHttp中最重要的一部,也是核心悍缠。

 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內(nèi)置的攔截器放到一個List集合中卦绣,然后把攔截器集合和Request一起創(chuàng)建了一個RealInterceptorChain對象,然后調(diào)用proceed方法把整個攔截器組合成鏈狀飞蚓。最終返回一個Response

責(zé)任鏈模式:一個請求沿著一條“鏈”傳遞滤港,直到該“鏈”上的某個處理者處理它為止。

  • client.interceptors() :
    自定義的攔截器
  • retryAndFollowUpInterceptor:
    失敗后重連或者服務(wù)器返回請求重新發(fā)起請求的攔截器玷坠。
  • BridgeInterceptor:
    鏈接客戶端代碼和網(wǎng)絡(luò)代碼的橋梁蜗搔,也就是說配置請求內(nèi)容(設(shè)置內(nèi)容長度,內(nèi)容編碼八堡,設(shè)置gzip壓縮樟凄,添加cookie,設(shè)置其他報頭)
  • CacheInterceptor
    緩存機制攔截器兄渺,也就是說有沒有滿足請求的緩存有的話返回Cache缝龄。當(dāng)服務(wù)器返回數(shù)據(jù)有變化時更新Cache,如果當(dāng)前Cache失效就刪除挂谍。
  • ConnectInterceptor:
    建立服務(wù)器連接叔壤,正式開啟了網(wǎng)絡(luò)請求,調(diào)用連接池口叙,開啟Socket鏈接炼绘。
  • networkInterceptors:
    配置 OkHttpClient 時設(shè)置
  • CallServerInterceptor
    向服務(wù)器發(fā)送請求,并最終返回Response對象供客戶端使用妄田。
如何讓整個鏈狀攔截器運轉(zhuǎn)起來的俺亮?咱們現(xiàn)在看一下proceed方法
//正式開始調(diào)用攔截器工作  
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection){

    //省略部分與本文無關(guān)的代碼

    // 調(diào)用鏈中的下一個攔截器
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

   //確保每個攔截器都調(diào)用了proceed方法()
      if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }
   //省略部分與本文無關(guān)的代碼
    return response;
  }

其中有個index變量驮捍,每次調(diào)用都加1,然后獲得下一個攔截器脚曾,procced方法并沒有用for循環(huán)來遍歷interceptors集合东且,而是重新創(chuàng)建了一個RealInterceptorChain對象,且新對象的index在原來RealInterceptorChain對象index之上進行index+1,并把新的攔截器鏈對象RealInterceptorChain交給當(dāng)前攔截器Interceptor 的intercept方法本讥;查看BridgeInterceptor珊泳、CacheInterceptor等Okhttp內(nèi)置攔截器就可以印證這一點:在它們intercept的內(nèi)部都調(diào)用了chain.proceed()方法,且每次調(diào)用都在會創(chuàng)建一個RealInterceptorChain對象拷沸。所以整個攔截器的工作流程是這樣的:


微信圖片_20180129200804.jpg

那么到此為止一個完整的Okttp請求的流程就已經(jīng)完成色查。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市堵漱,隨后出現(xiàn)的幾起案子综慎,更是在濱河造成了極大的恐慌,老刑警劉巖勤庐,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件示惊,死亡現(xiàn)場離奇詭異,居然都是意外死亡愉镰,警方通過查閱死者的電腦和手機米罚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丈探,“玉大人录择,你說我怎么就攤上這事⊥虢担” “怎么了隘竭?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長讼渊。 經(jīng)常有香客問我动看,道長,這世上最難降的妖魔是什么爪幻? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任菱皆,我火速辦了婚禮,結(jié)果婚禮上挨稿,老公的妹妹穿的比我還像新娘仇轻。我一直安慰自己,他們只是感情好奶甘,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布篷店。 她就那樣靜靜地躺著,像睡著了一般臭家。 火紅的嫁衣襯著肌膚如雪船庇。 梳的紋絲不亂的頭發(fā)上吭产,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天侣监,我揣著相機與錄音鸭轮,去河邊找鬼。 笑死橄霉,一個胖子當(dāng)著我的面吹牛窃爷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播姓蜂,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼按厘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钱慢?” 一聲冷哼從身側(cè)響起逮京,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎束莫,沒想到半個月后懒棉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡览绿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年策严,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饿敲。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡妻导,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怀各,到底是詐尸還是另有隱情倔韭,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布瓢对,位于F島的核電站寿酌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沥曹。R本人自食惡果不足惜份名,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一妓美、第九天 我趴在偏房一處隱蔽的房頂上張望僵腺。 院中可真熱鬧,春花似錦壶栋、人聲如沸辰如。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琉兜。三九已至凯正,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間豌蟋,已是汗流浹背廊散。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梧疲,地道東北人允睹。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像幌氮,于是被迫代替她去往敵國和親缭受。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

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