okHttp源碼分析(1)-分發(fā)器

一. okHttp簡(jiǎn)介

okhttp已經(jīng)成為Android開發(fā)中必不可少的網(wǎng)絡(luò)請(qǐng)求工具,無(wú)論在平時(shí)開發(fā)還是面試的過(guò)程中物独,都會(huì)有所涉及菱魔,弄清okhttp的使用流程拄丰,已經(jīng)是每一個(gè)android開發(fā)者成為高級(jí)工程師的必經(jīng)之路现斋。其實(shí)其原理并不復(fù)雜,本文將分兩篇來(lái)著重介紹okhttp中分發(fā)器和攔截器原理偎蘸。

二. 大綱

  1. okHttp的優(yōu)點(diǎn)以及請(qǐng)求過(guò)程
  2. Dispatcher的分發(fā)流程

三. okHttp的優(yōu)點(diǎn)以及請(qǐng)求過(guò)程

  1. 首先我們來(lái)看看使用okhttp都有哪些優(yōu)點(diǎn):
  • 支持HTTP/2并允許對(duì)同一主機(jī)的所有請(qǐng)求共享一個(gè)套接字
  • 通過(guò)連接池庄蹋,復(fù)用socket,減少請(qǐng)求延遲
  • 默認(rèn)通過(guò)gzip壓縮數(shù)據(jù)
  • 響應(yīng)緩存迷雪,避免重復(fù)請(qǐng)求網(wǎng)絡(luò)
  • 請(qǐng)求失敗自動(dòng)重試主機(jī)其它ip限书,自動(dòng)重定向
  1. okHttp的請(qǐng)求流程

使用okhttp的大致流程為:

  1. 創(chuàng)建一個(gè)OkHttpClient

  2. 創(chuàng)建一個(gè)Request對(duì)象

  3. 創(chuàng)建一個(gè)call對(duì)象,接受request

  4. 進(jìn)行請(qǐng)求執(zhí)行任務(wù)

  5. 內(nèi)部通過(guò)Dispatcher分發(fā)任務(wù)

  6. 五大默認(rèn)攔截器完成整個(gè)請(qǐng)求過(guò)程

  7. 返回結(jié)果

  8. 什么是分發(fā)器Dispatcher

    Dispatcher章咧,分發(fā)器就是來(lái)調(diào)配請(qǐng)求任務(wù)的倦西,內(nèi)部會(huì)包含一個(gè)線程池×扪希可以在創(chuàng)建OkHttpClient`時(shí)扰柠,傳遞我們自己定義的線程池來(lái)創(chuàng)建分發(fā)器。

    這個(gè)Dispatcher中的成員有:

    //異步請(qǐng)求同時(shí)存在的最大請(qǐng)求
    private int maxRequests = 64;
    //異步請(qǐng)求同一域名同時(shí)存在的最大請(qǐng)求
    private int maxRequestsPerHost = 5;
    //閑置任務(wù)(沒(méi)有請(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<>();
    
  9. get卤档,post請(qǐng)求流程

get請(qǐng)求流程:

//1.創(chuàng)建OkHttpClient對(duì)象
OkHttpClient okHttpClient = new OkHttpClient();
//2.創(chuàng)建Request對(duì)象,設(shè)置請(qǐng)求方式程剥。
Request request = new Request.Builder()
        .url("http://www.reibang.com/u/f260c485f077")
        .get()
        .build();
//3.創(chuàng)建一個(gè)call對(duì)象
Call call = okHttpClient.newCall(request);
//4.同步請(qǐng)求
new Thread(new Runnable() {
    @Override
    public void run() {
        
        Response response = call.execute();
       
     }}).start();
//5.異步請(qǐng)求
call.enqueue(new Callback() {
    //請(qǐng)求失敗執(zhí)行的方法
    @Override
    public void onFailure(Call call, IOException e) {
        String err = e.getMessage().toString();
    }

    //請(qǐng)求成功執(zhí)行的方法
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        inal String rtn = response.body().string();
    }
});

post請(qǐng)求流程:

//1.創(chuàng)建OkHttpClient對(duì)象,設(shè)置參數(shù)
OkHttpClient okHttpClient = new OkHttpClient();

FormBody.Builder mBuild = new FormBody.Builder();
mBuild.add("key1", "vaule1")
        .add("key2", "vaule2");
RequestBody requestBodyPost = mBuild.build();

//2.創(chuàng)建Request對(duì)象劝枣,設(shè)置請(qǐng)求方式。
Request request = new Request.Builder()
        .url("http://www.baidu.com")
        .post(requestBodyPost)
        .build();
//3.創(chuàng)建一個(gè)call對(duì)象
Call call = okHttpClient.newCall(request);
//4.請(qǐng)求回調(diào)方法
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {

    }

    //請(qǐng)求成功執(zhí)行的方法
    @Override
    public void onResponse(Call call, Response response) throws IOException {

    }
});

四. Dispatcher的分發(fā)流程

okHttp中Dispatcher的分發(fā)流程大致分為以下幾步:

  1. 調(diào)用execute或者enqueue加入任務(wù)
  2. Dispatcher根據(jù)條件判斷將當(dāng)前任務(wù)加入隊(duì)列 runningAsyncCalls(執(zhí)行隊(duì)列)還是readyAsyncCalls(等待隊(duì)列)
  3. runningAsyncCalls隊(duì)列調(diào)用線程池執(zhí)行任務(wù)织鲸,
  4. runningAsyncCalls執(zhí)行任務(wù)完成舔腾,根據(jù)條件獲取readyAsyncCalls中任務(wù)執(zhí)行

根據(jù)okhttp中Dispatcher的分發(fā)流程,我們有幾個(gè)問(wèn)題需要弄清楚:

Q: Dispatcher將請(qǐng)求分發(fā)到隊(duì)列過(guò)程中搂擦,如何決定放入ready還是running稳诚?

Q: 從ready移動(dòng)running的條件是什么?(如何移動(dòng)盾饮,從哪里移動(dòng))

Q: Dispatcher分發(fā)器線程池的工作行為是怎樣的采桃。

帶著以上三個(gè)問(wèn)題,我們可以看看源碼是如何實(shí)現(xiàn)的丘损。

第一個(gè)問(wèn)題 Dispatcher將請(qǐng)求分發(fā)到隊(duì)列過(guò)程中普办,如何決定放入ready還是running

當(dāng)我們要執(zhí)行一個(gè)異步請(qǐng)求的時(shí)候,會(huì)調(diào)用okhttp的enqueue方法徘钥,其內(nèi)部就會(huì)調(diào)用分發(fā)器的enqueue方法衔蹲,我們直接來(lái)看分發(fā)器的enqueue方法做了什么:

okhttp的enqueue方法:

@Override 
public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    // client就是OkHttpClient 通過(guò)client獲取Dispatcher 調(diào)用Dispatcher的enqueue
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

Dispatcher的enqueue方法:

private int maxRequests = 64;
private int maxRequestsPerHost = 5;

synchronized void enqueue(AsyncCall call) {
    //runningAsyncCalls.size() < maxRequests(最大正在請(qǐng)求的數(shù)量)
    //runningCallsForHost(call) < maxRequestsPerHost(同一域名最大正在請(qǐng)求的個(gè)數(shù)限制)
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) <                      maxRequestsPerHost) {
        runningAsyncCalls.add(call);
        executorService().execute(call);
    } else {
        readyAsyncCalls.add(call);
    }
  }

看到這里相信大家都能明白第一個(gè)問(wèn)題,由注解可以看到,Dispatcher將任務(wù)加入隊(duì)列的兩個(gè)條件舆驶,當(dāng)前最大請(qǐng)求數(shù)不超過(guò)64橱健,同一域名最大正在請(qǐng)求的個(gè)數(shù)限制不超過(guò)5,同時(shí)滿足這兩個(gè)的情況下沙廉,想請(qǐng)求加入執(zhí)行隊(duì)列runningAsyncCalls中拘荡,并立即執(zhí)行。不滿足條件加入等待隊(duì)列readyAsyncCalls中撬陵。

第二個(gè)問(wèn)題 從ready移動(dòng)running的條件是什么珊皿?(如何移動(dòng),從哪里移動(dòng))

然后我們?cè)倩剡^(guò)頭來(lái)看看AsyncCall是個(gè)什么巨税,我們的請(qǐng)求加入這個(gè)AsyncCall以后都做了什么蟋定,打開AsyncCal源碼

RealCall implements Call 

首先可以看到RealCall implements Call ,當(dāng)我們調(diào)用call.enqueue時(shí)草添,也就是再調(diào)用RealCall.enqueue,那么我們只要看RealCall.enqueue中做了什么即可驶兜。

@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 {
            responseCallback.onFailure(RealCall.this, e);
        }
    } finally {
        //請(qǐng)求執(zhí)行完畢,調(diào)用finish();
        client.dispatcher().finished(this);
    }
}

如何把ready移動(dòng)running远寸,那么肯定是running中任務(wù)已經(jīng)執(zhí)行完成抄淑,所以我們繼續(xù)看finnally中 client.dispatcher().finished(this);做了什么。

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
        //將當(dāng)前執(zhí)行完成的請(qǐng)求移除而晒,并判斷是否移除成功
        if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
        //從等待隊(duì)列中獲取請(qǐng)求加入執(zhí)行隊(duì)列
        if (promoteCalls) promoteCalls();
        runningCallsCount = runningCallsCount();
        idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
        idleCallback.run();
    }
}
private void promoteCalls() {
    //判斷正在執(zhí)行的隊(duì)列個(gè)數(shù)蝇狼,不滿足返回
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
   //判斷等待隊(duì)列是否有請(qǐng)求,不滿足返回
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
    //循環(huán)獲取等待隊(duì)列中的請(qǐng)求
    for (Iterator<RealCall.AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        RealCall.AsyncCall call = i.next();
        //判斷當(dāng)前call的host執(zhí)行個(gè)數(shù)是否小于maxRequestsPerHost
        if (runningCallsForHost(call) < maxRequestsPerHost) {
            i.remove();
            //將等待隊(duì)列中的call加入到runningAsyncCalls
            runningAsyncCalls.add(call);
            //開始執(zhí)行任務(wù)
            executorService().execute(call);
        }
        //判斷正在執(zhí)行的隊(duì)列個(gè)數(shù)倡怎,滿足就returen迅耘,不在繼續(xù)增加
        if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
}

以上代碼邏輯,注釋的很清晰监署。會(huì)通過(guò)相同的條件颤专,將等待隊(duì)列中的請(qǐng)求加入到執(zhí)行隊(duì)列

最后一個(gè)問(wèn)題,分發(fā)器線程池的工作行為是怎樣的钠乏。

看到這里栖秕,相信大家已經(jīng)對(duì)okhtt的分發(fā)流程了解個(gè)大概,此時(shí)我們已經(jīng)將請(qǐng)求加入到了隊(duì)列中晓避,而在分發(fā)執(zhí)行請(qǐng)求的過(guò)程中簇捍,okhttp是怎么來(lái)執(zhí)行這些任務(wù)的呢,還是繼續(xù)來(lái)看源碼俏拱,通過(guò)以上源碼的分析我們可以看到Dispatcher是通過(guò)executorService().execute(call)方法執(zhí)行任務(wù)暑塑,所以我們直接看executorService()方法的源碼

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

在OkHttp的分發(fā)器中的線程池定義如上,其實(shí)就和Executors.newCachedThreadPool()創(chuàng)建的線程一樣锅必。首先核心線程為0事格,表示線程池不會(huì)一直為我們緩存線程,線程池中所有線程都是在60s內(nèi)沒(méi)有工作就會(huì)被回收。而最大線程Integer.MAX_VALUE與等待隊(duì)列SynchronousQueue的組合能夠得到最大的吞吐量驹愚。即當(dāng)需要線程池執(zhí)行任務(wù)時(shí)远搪,如果不存在空閑線程不需要等待,馬上新建線程執(zhí)行任務(wù)逢捺!等待隊(duì)列的不同指定了線程池的不同排隊(duì)機(jī)制谁鳍。一般來(lái)說(shuō),等待隊(duì)列BlockingQueue有:ArrayBlockingQueue劫瞳、LinkedBlockingQueueSynchronousQueue棠耕。

假設(shè)向線程池提交任務(wù)時(shí),核心線程都被占用的情況下:

ArrayBlockingQueue:基于數(shù)組的阻塞隊(duì)列柠新,初始化需要指定固定大小。

? 當(dāng)使用此隊(duì)列時(shí)辉巡,向線程池提交任務(wù)恨憎,會(huì)首先加入到等待隊(duì)列中,當(dāng)?shù)却?duì)列滿了之后郊楣,再次提交任務(wù)憔恳,嘗試加入隊(duì)列就會(huì)失敗,這時(shí)就會(huì)檢查如果當(dāng)前線程池中的線程數(shù)未達(dá)到最大線程净蚤,則會(huì)新建線程執(zhí)行新提交的任務(wù)钥组。所以最終可能出現(xiàn)后提交的任務(wù)先執(zhí)行,而先提交的任務(wù)一直在等待今瀑。

LinkedBlockingQueue:基于鏈表實(shí)現(xiàn)的阻塞隊(duì)列程梦,初始化可以指定大小,也可以不指定橘荠。

? 當(dāng)指定大小后屿附,行為就和ArrayBlockingQueu一致。而如果未指定大小哥童,則會(huì)使用默認(rèn)的Integer.MAX_VALUE作為隊(duì)列大小挺份。這時(shí)候就會(huì)出現(xiàn)線程池的最大線程數(shù)參數(shù)無(wú)用,因?yàn)闊o(wú)論如何贮懈,向線程池提交任務(wù)加入等待隊(duì)列都會(huì)成功匀泊。最終意味著所有任務(wù)都是在核心線程執(zhí)行。如果核心線程一直被占朵你,那就一直等待各聘。

SynchronousQueue : 無(wú)容量的隊(duì)列。

? 使用此隊(duì)列意味著希望獲得最大并發(fā)量撬呢。因?yàn)闊o(wú)論如何伦吠,向線程池提交任務(wù),往隊(duì)列提交任務(wù)都會(huì)失敗。而失敗后如果沒(méi)有空閑的非核心線程毛仪,就會(huì)檢查如果當(dāng)前線程池中的線程數(shù)未達(dá)到最大線程搁嗓,則會(huì)新建線程執(zhí)行新提交的任務(wù)。完全沒(méi)有任何等待箱靴,唯一制約它的就是最大線程數(shù)的個(gè)數(shù)腺逛。因此一般配合Integer.MAX_VALUE就實(shí)現(xiàn)了真正的無(wú)等待。

但是需要注意的時(shí)衡怀,我們都知道棍矛,進(jìn)程的內(nèi)存是存在限制的,而每一個(gè)線程都需要分配一定的內(nèi)存抛杨。所以線程并不能無(wú)限個(gè)數(shù)够委。那么當(dāng)設(shè)置最大線程數(shù)為Integer.MAX_VALUE時(shí),OkHttp同時(shí)還有最大請(qǐng)求任務(wù)執(zhí)行個(gè)數(shù): 64的限制怖现。這樣即解決了這個(gè)問(wèn)題同時(shí)也能獲得最大吞吐茁帽。

分發(fā)器的流程以及原理就到這里,okhttp每個(gè)版本代碼可能會(huì)略有不同屈嗤,本文基于okhttp3.6.0源碼進(jìn)行分析潘拨。下一篇我們將介紹okhttp中的攔截器是如何實(shí)現(xiàn)的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饶号,一起剝皮案震驚了整個(gè)濱河市铁追,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茫船,老刑警劉巖琅束,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異算谈,居然都是意外死亡狰闪,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門濒生,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)埋泵,“玉大人,你說(shuō)我怎么就攤上這事罪治±錾” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵觉义,是天一觀的道長(zhǎng)雁社。 經(jīng)常有香客問(wèn)我,道長(zhǎng)晒骇,這世上最難降的妖魔是什么霉撵? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任磺浙,我火速辦了婚禮,結(jié)果婚禮上徒坡,老公的妹妹穿的比我還像新娘撕氧。我一直安慰自己,他們只是感情好喇完,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布伦泥。 她就那樣靜靜地躺著,像睡著了一般锦溪。 火紅的嫁衣襯著肌膚如雪不脯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天刻诊,我揣著相機(jī)與錄音防楷,去河邊找鬼。 笑死则涯,一個(gè)胖子當(dāng)著我的面吹牛域帐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播是整,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼民假!你這毒婦竟也來(lái)了浮入?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤羊异,失蹤者是張志新(化名)和其女友劉穎事秀,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體野舶,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡易迹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了平道。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睹欲。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖一屋,靈堂內(nèi)的尸體忽然破棺而出窘疮,到底是詐尸還是另有隱情,我是刑警寧澤冀墨,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布闸衫,位于F島的核電站,受9級(jí)特大地震影響诽嘉,放射性物質(zhì)發(fā)生泄漏蔚出。R本人自食惡果不足惜弟翘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骄酗。 院中可真熱鬧稀余,春花似錦、人聲如沸酥筝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嘿歌。三九已至掸掏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宙帝,已是汗流浹背丧凤。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留步脓,地道東北人愿待。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像靴患,于是被迫代替她去往敵國(guó)和親仍侥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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