OKHttp(一)dispatcher分發(fā)器

簡(jiǎn)介

OkHttp是當(dāng)下Android使用最頻繁的網(wǎng)絡(luò)請(qǐng)求框架闽巩,由Square公司開源品山。Google在Android4.4以后開始將源碼中的HttpURLConnection底層實(shí)現(xiàn)替換為OKHttp,同時(shí)現(xiàn)在流行的Retrofit框架底層同樣是使用OKHttp的黍析。

優(yōu)點(diǎn):

  • 支持Spdy、Http1.X苫纤、Http2互订、Quic以及WebSocket
  • 連接池復(fù)用底層TCP(Socket)吱肌,減少請(qǐng)求延時(shí)
  • 無縫的支持GZIP減少數(shù)據(jù)流量
  • 緩存響應(yīng)數(shù)據(jù)減少重復(fù)的網(wǎng)絡(luò)請(qǐng)求
  • 請(qǐng)求失敗自動(dòng)重試主機(jī)的其他ip,自動(dòng)重定向
    …….

tip:本文版本為OkHttp 3.10.0仰禽,最新版本為:4.0.1氮墨,邏輯與3版本并沒有太大變化,但是改為kotlin實(shí)現(xiàn)吐葵。

   OkHttpClient client = new OkHttpClient();

   void syncGet(String url) throws IOException {
       Request request = new Request.Builder()
               .url(url)
               .build();

       // 執(zhí)行同步請(qǐng)求
       Call call = client.newCall(request);
       Response response = call.execute();
       
       // 獲得響應(yīng)
       ResponseBody body = response.body();
       System.out.println(body.string());
   }

   void AsyncGet(String url) {
       Request request = new Request.Builder()
               .url(url)
               .build();

       // 執(zhí)行異步請(qǐng)求
       Call call = client.newCall(request);
       call.enqueue(new Callback() {
           @Override
           public void onFailure(Call call, IOException e) {}

           @Override
           public void onResponse(Call call, Response response) throws IOException {
               // 獲得響應(yīng)
               ResponseBody body = response.body();
               System.out.println(body.string());
           }
       });
   }

使用起來很簡(jiǎn)單规揪,我們用一張流程圖描述一下

使用流程.png

在使用Okhttp發(fā)送一次請(qǐng)求時(shí),對(duì)于使用者最少要用到OkHttpClient温峭、RequestCall這3個(gè)對(duì)象猛铅;
OkHttpClient 中是一些配置信息,比如代理的配置诚镰,ssl證書配置等等奕坟;
Request 是封裝請(qǐng)求參數(shù)信息,比如請(qǐng)求地址清笨、請(qǐng)求方法、請(qǐng)求頭刃跛、請(qǐng)求體等等抠艾;
Call 本身是一個(gè)接口,實(shí)現(xiàn)類為RealCall桨昙,execute()是負(fù)責(zé)同步請(qǐng)求检号,enqueue()是負(fù)責(zé)異步請(qǐng)求,兩者唯一區(qū)別在于一個(gè)會(huì)直接發(fā)起網(wǎng)絡(luò)請(qǐng)求蛙酪,而另一個(gè)使用OkHttp內(nèi)置的線程池來進(jìn)行齐苛。
請(qǐng)求任務(wù)是如何分配的呢?這就涉及到OkHttp的任務(wù)分發(fā)器桂塞。

Dispatcher 分發(fā)器

分發(fā)器就是來調(diào)配請(qǐng)求任務(wù)的凹蜂,內(nèi)部會(huì)包含一個(gè)線程池以及任務(wù)隊(duì)列。在創(chuàng)建OkHttpClient時(shí)阁危,我們也可以傳遞自己定義的線程池來創(chuàng)建分發(fā)器玛痊。我看一下Dispatcher 的成員屬性:

public final class Dispatcher {  
    // 異步請(qǐng)求最大同時(shí)請(qǐng)求數(shù)量
    private int maxRequests = 64;
    // 異步請(qǐng)求同一域名同時(shí)存在的最大請(qǐng)求數(shù)量
    private int maxRequestsPerHost = 5;
    // 閑置任務(wù)(沒有請(qǐng)求時(shí)可執(zhí)行一些任務(wù),由使用者設(shè)置) 
    private @Nullable
    Runnable idleCallback;
    //異步請(qǐng)求使用的線程池
    private @Nullable
    ExecutorService executorService;
    //異步請(qǐng)求等待執(zhí)行隊(duì)列
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
    //異步請(qǐng)求正在執(zhí)行隊(duì)列
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
    //同步請(qǐng)求正在執(zhí)行隊(duì)列
    private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
    // 上面說的可配置線程池的構(gòu)造函數(shù)
    public Dispatcher(ExecutorService executorService) {
        this.executorService = executorService;
    }

    public Dispatcher() {}
    // 創(chuàng)建線程池(懶加載)
    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;
    }
}

execute() 同步請(qǐng)求

這是我們來看一下我們使用的時(shí)候調(diào)用Call 對(duì)象的同步方法execute()做了什么(實(shí)際上調(diào)用的是Call的實(shí)現(xiàn)類RealCall的execute方法):

    @Override
    public Response execute() throws IOException {
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        try {
            // 我們先看這里狂打,調(diào)用了dispatcher的executed方法
            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 {
            // 任務(wù)結(jié)束或者出現(xiàn)異常擂煞,將任務(wù)標(biāo)記為結(jié)束
            client.dispatcher().finished(this);
        }
    }

很明顯調(diào)用了dispatcher的executed()方法,ok再跟到dispatcher中:

    synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
    }

就一句話趴乡,將call添加到正在運(yùn)行的同步隊(duì)列中对省,表示當(dāng)前的請(qǐng)求任務(wù)已經(jīng)開始執(zhí)行了蝗拿,等到請(qǐng)求完成再把它從隊(duì)列中移除 (dispatcher中的finished方法)。

enqueue() 異步請(qǐng)求

再看一下RealCall的enqueue方法

    @Override
    public void enqueue(Callback responseCallback) {
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        // 調(diào)用Dispatcher中的enqueue方法
        client.dispatcher().enqueue(new AsyncCall(responseCallback));
    }

同樣的調(diào)用了Dispatcher中的enqueue方法蒿涎,看一眼:

    synchronized void enqueue(AsyncCall call) {
        // 如果同時(shí)執(zhí)行的請(qǐng)求數(shù)量不超過64蛹磺,同時(shí)同一域名主機(jī)的請(qǐng)求數(shù)量不超過5個(gè)
        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
            // 滿足條件將請(qǐng)求加入正在運(yùn)行的隊(duì)列中,并且開始執(zhí)行
            runningAsyncCalls.add(call);
            executorService().execute(call);
        } else {
            // 不滿足條件將請(qǐng)求加入等待隊(duì)列
            readyAsyncCalls.add(call);
        }
    }

上面的描述已經(jīng)很詳細(xì)了同仆,那么等待隊(duì)列的請(qǐng)求啥時(shí)候才能被臨幸呢萤捆?有沒有注意到
Dispatcher的enqueue方法的參數(shù)是AsyncCall,其實(shí)他就是一個(gè)Runnable俗批。

    final class AsyncCall extends NamedRunnable {
        private final Callback responseCallback;

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

        .....

        @Override
        protected void execute() {
            boolean signalledCallback = false;
            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) {
                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 {
                // 當(dāng)請(qǐng)求執(zhí)行完成調(diào)用了Dispatcher的finished方法
                client.dispatcher().finished(this);
            }
        }
    }

和同步請(qǐng)求一樣俗或,當(dāng)一個(gè)異步請(qǐng)求結(jié)束也調(diào)用了Dispatcher的finished方法,看一眼:

    void finished(AsyncCall call) {
        finished(runningAsyncCalls, call, true);
    }

調(diào)用重載的finished:

    private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
            // 先把當(dāng)前的請(qǐng)求移出正在執(zhí)行隊(duì)列
            if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
            // promoteCalls這里是true岁忘, 執(zhí)行promoteCalls()
            if (promoteCalls) promoteCalls();
            runningCallsCount = runningCallsCount();
            idleCallback = this.idleCallback;
        }

        if (runningCallsCount == 0 && idleCallback != null) {
            idleCallback.run();
        }
    }

先把當(dāng)前的請(qǐng)求移出正在執(zhí)行隊(duì)列辛慰,然后執(zhí)行了promoteCalls(),八成我們想要的就在這里了:

    private void promoteCalls() {
        // 如果同時(shí)執(zhí)行的請(qǐng)求數(shù)量不超過64個(gè)干像,直接return
        if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
        // 如果等待隊(duì)列是空的帅腌,return!
        if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
        // 遍歷等待隊(duì)列
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall call = i.next();
            // 檢查一下正在執(zhí)行的同一host的請(qǐng)求數(shù)量是不是不滿5個(gè)
            if (runningCallsForHost(call) < maxRequestsPerHost) {
                // 滿足條件麻汰,移出等待隊(duì)列速客,加入正在執(zhí)行隊(duì)列,直接執(zhí)行請(qǐng)求任務(wù)五鲫!
                i.remove();
                runningAsyncCalls.add(call);
                executorService().execute(call);
            }
            // 正在執(zhí)行隊(duì)列滿了溺职,return 吧!
            if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
        }
    }

這里就是我們要找的位喂,當(dāng)一個(gè)異步任務(wù)執(zhí)行完成時(shí)浪耘,檢查正在執(zhí)行隊(duì)列有沒有空位置,遍歷等待隊(duì)列塑崖,把其中滿足條件的請(qǐng)求任務(wù)拿出來七冲,放到正在執(zhí)行隊(duì)列中去執(zhí)行,執(zhí)行完了再次檢查规婆,如此循環(huán)澜躺。

Dispatcher中的線程池

Dispatcher中的線程池很有意思,我們來看一下

 public synchronized ExecutorService executorService() {
        if (executorService == null) {
            // 核心線程數(shù)0聋呢,最大線程數(shù)Integer.MAX_VALUE苗踪,空閑線程閑置時(shí)間 60,        
            // 閑置時(shí)間單位 秒削锰,線程等待隊(duì)列 SynchronousQueue通铲, 以及線程創(chuàng)建工廠
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher",
                    false));
        }
        return executorService;
    }

這是Dispatcher中默認(rèn)創(chuàng)建的線程池,注意里面幾個(gè)關(guān)鍵的參數(shù)器贩,第一個(gè)參數(shù)核心線程數(shù)量0颅夺,最大線程數(shù) Integer.MAX_VALUE 和隊(duì)列 SynchronousQueue朋截。
為啥要這么配置呢?
首先核心線程為0吧黄,表示線程池就不用一直為我們緩存線程部服,線程池中所有線程都是在60s內(nèi)沒有工作就會(huì)被回收,在一些網(wǎng)絡(luò)請(qǐng)求不頻繁的app上可以節(jié)約一些內(nèi)存拗慨。而最大線程Integer.MAX_VALUE與等待隊(duì)列SynchronousQueue的組合能夠得到最大的吞吐量廓八,即當(dāng)需要線程池執(zhí)行任務(wù)時(shí),如果不存在空閑線程不需要等待赵抢,馬上新建線程執(zhí)行任務(wù)剧蹂!來分析一下:
SynchronousQueue 是一種無容量的隊(duì)列,當(dāng)向線程池中加入新請(qǐng)求時(shí)烦却,只會(huì)有以下幾種情況:

  • 由于沒有核心線程宠叼,第一次進(jìn)來的請(qǐng)求會(huì)直接創(chuàng)建新線程執(zhí)行。
  • 此時(shí)有未被回收的空閑線程其爵,復(fù)用該線程執(zhí)行請(qǐng)求冒冬。
  • 沒有空閑線程了,新請(qǐng)求需要添加到隊(duì)列摩渺,但是隊(duì)列的容量為0简烤,相當(dāng)于滿了,此時(shí)要判斷當(dāng)前正在執(zhí)行的線程數(shù)是否大于最大線程數(shù)证逻,由于最大線程數(shù)為Integer.MAX_VALUE乐埠,肯定不會(huì)超過,所以要新建一個(gè)線程執(zhí)行請(qǐng)求囚企。

可以得出結(jié)論,我們的請(qǐng)求任務(wù)是可以立即執(zhí)行的瑞眼,其實(shí)還有另外2種阻塞隊(duì)列:ArrayBlockingQueueLinkedBlockingQueue龙宏,但是它們一般都有容量,當(dāng)請(qǐng)求任務(wù)進(jìn)入等待隊(duì)列后伤疙,可能會(huì)出現(xiàn)以下幾種情況:

  • 沒有空閑線程了银酗,新請(qǐng)求添加到隊(duì)列,隊(duì)列中的請(qǐng)求一直在等待空閑線程徒像,出現(xiàn)阻塞黍特。
  • 沒有空閑線程了,等待隊(duì)列也滿了锯蛀,正在執(zhí)行的線程數(shù)小于最大線程數(shù)灭衷,新建線程執(zhí)行請(qǐng)求,出現(xiàn)后提交的任務(wù)先執(zhí)行旁涤,而先提交的任務(wù)一直在等待的情況翔曲,同樣阻塞了迫像。
  • 沒有空閑線程了,等待隊(duì)列也滿了瞳遍,正在執(zhí)行的線程數(shù)大于等于最大線程數(shù)闻妓,請(qǐng)求被拒絕了,尷尬掠械。

總結(jié)一句話由缆,Dispatcher的默認(rèn)線程池參數(shù)配置保證了新建的請(qǐng)求都可以被立即執(zhí)行,避免阻塞猾蒂。
當(dāng)然需要注意的時(shí)均唉,進(jìn)程的內(nèi)存是存在限制的,而每一個(gè)線程都需要分配一定的內(nèi)存婚夫。所以線程并不能無限個(gè)數(shù)浸卦。那么當(dāng)設(shè)置最大線程數(shù)為Integer.MAX_VALUE時(shí),OkHttp同時(shí)還有最大請(qǐng)求任務(wù)執(zhí)行個(gè)數(shù): 64的限制案糙,這樣既解決了這個(gè)問題同時(shí)也能獲得最大吞吐限嫌。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市时捌,隨后出現(xiàn)的幾起案子怒医,更是在濱河造成了極大的恐慌,老刑警劉巖奢讨,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稚叹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拿诸,警方通過查閱死者的電腦和手機(jī)扒袖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亩码,“玉大人季率,你說我怎么就攤上這事∶韫担” “怎么了飒泻?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吏廉。 經(jīng)常有香客問我泞遗,道長,這世上最難降的妖魔是什么席覆? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任史辙,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘髓霞。我一直安慰自己卦睹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布方库。 她就那樣靜靜地躺著结序,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纵潦。 梳的紋絲不亂的頭發(fā)上徐鹤,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音邀层,去河邊找鬼返敬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛寥院,可吹牛的內(nèi)容都是我干的劲赠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼秸谢,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼凛澎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起估蹄,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤塑煎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后臭蚁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體最铁,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年垮兑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冷尉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡系枪,死狀恐怖网严,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嗤无,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布怜庸,位于F島的核電站当犯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏割疾。R本人自食惡果不足惜嚎卫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拓诸,春花似錦侵佃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至倍谜,卻和暖如春迈螟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尔崔。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國打工答毫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人季春。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓洗搂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親载弄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耘拇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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