OkHttp 源碼剖析系列(一)——請(qǐng)求的發(fā)起及攔截器機(jī)制概述

系列索引

本系列文章基于 OkHttp3.14

OkHttp 源碼剖析系列(一)——請(qǐng)求的發(fā)起及攔截器機(jī)制概述

OkHttp 源碼剖析系列(二)——攔截器大體流程分析

OkHttp 源碼剖析系列(三)——緩存機(jī)制分析

OkHttp 源碼剖析系列(四)——連接的建立概述

OkHttp 源碼剖析系列(五)——路由選擇機(jī)制

OkHttp 源碼剖析系列(六)——連接復(fù)用機(jī)制及連接的建立

OkHttp 源碼剖析系列(七)——請(qǐng)求的發(fā)起及響應(yīng)的讀取

前言

OkHttp 是一個(gè)我從學(xué) Android 開(kāi)始就接觸的網(wǎng)絡(luò)請(qǐng)求庫(kù)了雁刷,想想現(xiàn)在也陪伴它快兩年了,卻沒(méi)有系統(tǒng)性地對(duì)它進(jìn)行過(guò)一次系統(tǒng)性的源碼解析。因此準(zhǔn)備開(kāi)設(shè)這樣一個(gè)系列寂诱,對(duì) OkHttp 的源碼進(jìn)行解析强戴。

此篇源碼解析基于 OkHttp 3.14

OkHttpClient

我們都知道,使用 OkHttp 我們首先需要?jiǎng)?chuàng)建并獲得一個(gè) OkHttpClientOkHttpClient 是 OkHttp 中十分重要的一個(gè)類沼本,下面是官方在 Java Doc 中對(duì)它的介紹:

Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their
responses.

OkHttpClients should be shared
OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for
all of your HTTP calls. This is because each client holds its own connection pool and thread
pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a
client for each request wastes resources on idle pools.

根據(jù)官方對(duì)其的介紹可以看出兢交,它是一個(gè) Call 的工廠類薪捍,可以用它來(lái)生產(chǎn) Call,從而通過(guò) Call 來(lái)發(fā)起 HTTP Request 獲取 Response。

同時(shí)酪穿,官方推薦的使用方式是使用一個(gè)全局的 OkHttpClient 在多個(gè)類之間共享凳干。因?yàn)槊總€(gè) Client 都會(huì)有一個(gè)自己的連接池和線程池,復(fù)用 Client 可以減少資源的浪費(fèi)被济。

它的構(gòu)建采用了 Builder 模式救赐,提供了許多可供我們配置的參數(shù):

public static final class Builder {
    Dispatcher dispatcher;
    @Nullable
    Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;
    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    EventListener.Factory eventListenerFactory;
    ProxySelector proxySelector;
    CookieJar cookieJar;
    @Nullable
    Cache cache;
    @Nullable
    InternalCache internalCache;
    SocketFactory socketFactory;
    @Nullable
    SSLSocketFactory sslSocketFactory;
    @Nullable
    CertificateChainCleaner certificateChainCleaner;
    HostnameVerifier hostnameVerifier;
    CertificatePinner certificatePinner;
    Authenticator proxyAuthenticator;
    Authenticator authenticator;
    ConnectionPool connectionPool;
    Dns dns;
    boolean followSslRedirects;
    boolean followRedirects;
    boolean retryOnConnectionFailure;
    int callTimeout;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;
    int pingInterval;
    // ...
}

可以看到,它的可配置的參數(shù)還是非常多的只磷。

構(gòu)建了 OkHttpClient 之后经磅,我們可以通過(guò) OkHttpClient.newCall 方法根據(jù)我們傳入的 Request 創(chuàng)建對(duì)應(yīng)的 Call

/**
 * Prepares the {@code request} to be executed at some point in the future.
 */
@Override
public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}

Request

Request 所對(duì)應(yīng)的就是我們 HTTP 請(qǐng)求中的 Request钮追,可以對(duì)它的 url预厌、method、header 等在 Builder 中進(jìn)行配置元媚。

Request 的構(gòu)建同樣采用了 Builder 模式進(jìn)行構(gòu)建:

public static class Builder {
    @Nullable
    HttpUrl url;
    String method;
    Headers.Builder headers;
    @Nullable
    RequestBody body;
    // ...
}

構(gòu)建完 Request 后轧叽,就可以調(diào)用 OkHttpClient.newCall 方法創(chuàng)建對(duì)應(yīng) Call

Call

構(gòu)建

我們知道,newCall 方法中調(diào)用了 RealCall.newRealCall(this, request, false /* for web socket */);刊棕,其中第三個(gè)參數(shù)代表是否使用 web socket炭晒。

我們看看 RealCall.newRealCall 方法:

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
}

這里根據(jù)我們傳入的參數(shù)構(gòu)建了一個(gè) RealCall 對(duì)象,并根據(jù) client 構(gòu)建了其 transmitter鞠绰。

RealCall 的構(gòu)造函數(shù)中主要是一些賦值:

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
}

Transmitter 中也是一些賦值操作:

public Transmitter(OkHttpClient client, Call call) {
    this.client = client;
    this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
    this.call = call;
    this.eventListener = client.eventListenerFactory().create(call);
    this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
} 

其中首先調(diào)用了 Internal.instance.realConnectionPool 方法腰埂,通過(guò) client.connectionPool 獲取到了 RealConnectionPool 對(duì)象,之后調(diào)用了 client.eventListenerFactory().create(call) 方法構(gòu)創(chuàng)建了其 eventListener蜈膨。

請(qǐng)求的發(fā)起

OkHttp 的執(zhí)行有兩種方式屿笼,enqueueexecute,它們分別代表了異步請(qǐng)求與同步請(qǐng)求:

  • enqueue:代表了異步請(qǐng)求翁巍,不會(huì)阻塞調(diào)用線程驴一。需要我們傳入一個(gè) Callback,當(dāng)請(qǐng)求成功時(shí)灶壶,會(huì)回調(diào)其 onResponse 方法肝断,請(qǐng)求失敗時(shí)則會(huì)回調(diào)其 onFailure 方法。

  • execute:代表了同步請(qǐng)求驰凛,會(huì)阻塞調(diào)用線程胸懈,請(qǐng)求結(jié)束后直接返回請(qǐng)求結(jié)果。

讓我們分別對(duì)其進(jìn)行分析:

異步請(qǐng)求

我們先分析一下 enqueue 方法:

@Override
public void enqueue(Callback responseCallback) {
    synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    // 通知eventListener
    transmitter.callStart();
    // 構(gòu)建AsyncCall并分派任務(wù)
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

它首先調(diào)用了 transmitter.callStart恰响,最后調(diào)用到了之前構(gòu)造的 eventListenercallStart 方法

之后它調(diào)用了 client.dispatcher().enqueue 方法趣钱,構(gòu)建了一個(gè) AsyncCall 對(duì)象后交給了 client.dispatcher 進(jìn)行任務(wù)的分派。

executeOn

AsyncCall 類對(duì)外暴露了 executeOn 方法胚宦,Dispatcher 可以通過(guò)調(diào)用該方法并傳入 ExecutorService 使其在該線程池所提供的線程中發(fā)起 HTTP 請(qǐng)求首有,獲取 Response 并回調(diào) Callback 的對(duì)應(yīng)方法從而實(shí)現(xiàn)任務(wù)的調(diào)度燕垃。

void executeOn(ExecutorService executorService) {
    assert (!Thread.holdsLock(client.dispatcher()));
    boolean success = false;
    try {
        // 在對(duì)應(yīng)的ExecutorService中執(zhí)行該AsyncCall
        executorService.execute(this);
        success = true;
    } catch (RejectedExecutionException e) {
        // 出現(xiàn)問(wèn)題,調(diào)用Callback對(duì)應(yīng)方法
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        transmitter.noMoreExchanges(ioException);
        responseCallback.onFailure(RealCall.this, ioException);
    } finally {
        // 不論是否成功井联,通知Dispatcher請(qǐng)求完成
        if (!success) {
            client.dispatcher().finished(this); // This call is no longer running!
        }
    }
}

可以看出卜壕,AsyncCall 是一個(gè) Runnable,我們看看它實(shí)現(xiàn)的 execute 方法:

@Override
protected void execute() {
    boolean signalledCallback = false;
    // 開(kāi)始Timeout計(jì)時(shí)
    transmitter.timeoutEnter();
    try {
        // 獲取Response
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
        // 請(qǐng)求成功烙常,通知Callback
        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 {
            // 請(qǐng)求失敗轴捎,通知Callback
            responseCallback.onFailure(RealCall.this, e);
        }
    } finally {
        // 不論是否成功,通知Dispatcher請(qǐng)求完成
        client.dispatcher().finished(this);
    }
}

這里首先調(diào)用了 transmitter.timeoutEnter() 方法開(kāi)始了 Timeout 的計(jì)時(shí)军掂。

之后若請(qǐng)求成功轮蜕,則會(huì)通過(guò) getResponseWithInterceptorChain 方法獲取了 Response昨悼,之后調(diào)用 Callback.onResponse 方法通知請(qǐng)求成功蝗锥。

若請(qǐng)求失敗,會(huì)調(diào)用 Callback.onFailure 方法通知請(qǐng)求失敗率触。

看來(lái)網(wǎng)絡(luò)請(qǐng)求的核心實(shí)現(xiàn)在 getResponseWithInterceptorChain 方法中實(shí)現(xiàn)终议,而 OkHttp 的超時(shí)機(jī)制與 transmitter.timeoutEnter 有關(guān),我們暫時(shí)先不關(guān)注這些細(xì)節(jié)葱蝗。

異步線程池

讓我們來(lái)看看 OkHttp 對(duì)異步請(qǐng)求采用了怎樣的線程池穴张。調(diào)用者在 AsyncCall.executeOn 方法中傳入了 Dispatcher.executorService 方法的返回值,我們來(lái)到此方法:

public synchronized ExecutorService executorService() {
    if (executorService == null) {
        executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
}

我們知道两曼,這里的線程池是可以通過(guò)創(chuàng)建 Dispatcher 時(shí)指定的皂甘,若不指定,則這里會(huì)創(chuàng)建一個(gè)如上代碼中的線程池悼凑,我們來(lái)分析一下它的幾個(gè)參數(shù)偿枕。

  • 核心線程數(shù) corePoolSize:保持在線程池中的線程數(shù),即使空閑后也不會(huì)保留户辫。由于為 0渐夸,因此任何線程空閑時(shí)都不會(huì)被保留。
  • 最大線程數(shù) maximumPoolSize:線程池最大支持創(chuàng)建的線程數(shù)渔欢,這里指定了 Integer.MAX_VALUE墓塌。
  • 線程存活時(shí)間 keepAliveTime:線程空閑后所能存活的時(shí)間,若超過(guò)該時(shí)間就會(huì)被回收奥额。這里指定的時(shí)間為 60 個(gè)時(shí)間單位(60s)苫幢,也就是說(shuō)線程在空閑超過(guò) 60s 后就會(huì)被回收。
  • 時(shí)間單位 unit:簽名的線程存活時(shí)間的單位垫挨,這里為 TimeUnit.SECONDS韩肝,也就是說(shuō)秒
  • 線程等待隊(duì)列 workQueue:線程的等待隊(duì)列,里面的元素會(huì)按序排隊(duì)棒拂,依次執(zhí)行伞梯,這里和指定的是 SynchronousQueue玫氢。
  • 線程工廠 threadFactory:線程的創(chuàng)建工廠這里傳入的是 Util.threadFactory 方法創(chuàng)建的線程工廠。

對(duì)于上面的幾個(gè)參數(shù)谜诫,我們有幾個(gè)細(xì)節(jié)需要考慮一下:

為什么要采用 SynchronousQueue

首先我們先需要了解一下什么是 SynchronousQueue漾峡,它雖然是一個(gè)隊(duì)列,但它內(nèi)部不存在任何的容器喻旷,它采用了一種經(jīng)典的生產(chǎn)者-消費(fèi)者模型生逸,它有多個(gè)生產(chǎn)者和消費(fèi)者,當(dāng)一個(gè)生產(chǎn)線程進(jìn)行生產(chǎn)操作(put)時(shí)且预,若沒(méi)有消費(fèi)者線程進(jìn)行消費(fèi)(take)槽袄,那么該線程會(huì)阻塞,直到有消費(fèi)者進(jìn)行消費(fèi)锋谐。也就是說(shuō)遍尺,它僅僅實(shí)現(xiàn)了一個(gè)傳遞的操作,這種傳遞功能由于沒(méi)有了中間的放入容器涮拗,再?gòu)娜萜髦腥〕龅倪^(guò)程乾戏,因此是一種快速傳遞元素的方式就漾,這對(duì)于我們網(wǎng)絡(luò)請(qǐng)求這種高頻請(qǐng)求來(lái)說(shuō)懊蒸,是十分合適的妒貌。關(guān)于 SynchronousQueue 可以看這篇文章: java并發(fā)之SynchronousQueue實(shí)現(xiàn)原理

為什么線程池采用這種線程數(shù)量不設(shè)上限逆害,每個(gè)線程空閑時(shí)只存活很短時(shí)間的策略

實(shí)際上在 OkHttp 的設(shè)計(jì)中纯陨,將線程的個(gè)數(shù)的維護(hù)工作不再交給線程池活喊,而是由 Dispatcher 進(jìn)行實(shí)現(xiàn)软棺,通過(guò)外部所設(shè)置的 maxRequestsmaxRequestsPerHost 來(lái)調(diào)整等待隊(duì)列及執(zhí)行隊(duì)列尤勋,從而實(shí)現(xiàn)對(duì)線程最大數(shù)量的控制喘落。具體 Dispatcher 的實(shí)現(xiàn)在本文后面會(huì)講到。

同步請(qǐng)求

execute

我們接著看到 execute 方法最冰,看看同步請(qǐng)求的執(zhí)行:

@Override
public Response execute() throws IOException {
    synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
        // 通知Dispatcher
        client.dispatcher().executed(this);
        // 獲取Response
        return getResponseWithInterceptorChain();
    } finally {
        // 不論是否成功瘦棋,通知Dispatcher請(qǐng)求完成
        client.dispatcher().finished(this);
    }
}

它首先調(diào)用了 Dispatcher.executed 方法,通知 Dispatcher 該 Call 被執(zhí)行暖哨,之后調(diào)用到了 getResponseWithInterceptorChain 方法獲取 Response赌朋,不論是否成功都會(huì)調(diào)用 Dispatcher.finished 通知 Dispatcher 該 Call 執(zhí)行完成。

Dispatcher 任務(wù)調(diào)度

enqueue

我們看看 Dispatcher 是如何調(diào)度異步請(qǐng)求的篇裁,來(lái)到 Dispatcher.enqueue 方法:

void enqueue(AsyncCall call) {
    synchronized (this) {
        // 加入等待隊(duì)列
        readyAsyncCalls.add(call);
        // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
        // the same host.
        if (!call.get().forWebSocket) {
            // 尋找同一個(gè)host的Call
            AsyncCall existingCall = findExistingCallWithHost(call.host());
            // 復(fù)用Call的callsPerHost
            if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
        }
    }
    // 嘗試執(zhí)行等待隊(duì)列中的任務(wù)
    promoteAndExecute();
}

這里先將其加入了 readAsyncCalls 這一等待隊(duì)列中沛慢。

之后調(diào)用了 findExistingCallWithHost 方法嘗試尋找 host 相同的 Call,它會(huì)遍歷 readyAsyncCallsrunningAsyncCalls 兩個(gè)隊(duì)列尋找 host 相同的 Call达布。

若找到了對(duì)應(yīng)的 Call团甲,則會(huì)調(diào)用 call.reuseCallsPerHostFrom 來(lái)復(fù)用這個(gè) Call 的 callsPerHost,從而便于統(tǒng)計(jì)一個(gè) host 對(duì)應(yīng)的 Call 的個(gè)數(shù)黍聂,它是一個(gè) AtomicInteger躺苦。

最后會(huì)調(diào)用 promoteAndExecute 方法身腻,這個(gè)方法會(huì)嘗試將等待隊(duì)列中的任務(wù)執(zhí)行。

executed

我們繼續(xù)看看 Dispatcher 是如何調(diào)度同步請(qǐng)求的匹厘,來(lái)到 Dispatcher.executed 方法:

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

這里很簡(jiǎn)單霸株,直接將該同步任務(wù)加入了執(zhí)行隊(duì)列 runningSyncCalls 中。

promoteAndExecute

我們看到 promoteAndExecute 方法:

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));
    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
            i.remove();
            // 增加host對(duì)應(yīng)執(zhí)行中call的數(shù)量
            asyncCall.callsPerHost().incrementAndGet();
            executableCalls.add(asyncCall);
            runningAsyncCalls.add(asyncCall);
        }
        isRunning = runningCallsCount() > 0;
    }
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
        AsyncCall asyncCall = executableCalls.get(i);
        asyncCall.executeOn(executorService());
    }
    return isRunning;
}

在這個(gè)方法中遍歷了 readyAsyncCalls 隊(duì)列集乔,不斷地尋找能夠執(zhí)行的 AsynCall去件,若找到則會(huì)在最后統(tǒng)一調(diào)用 AsyncCall.executeOn 方法在自己的 executorService 線程池中執(zhí)行該 Call。其中扰路,執(zhí)行中的任務(wù)不能超過(guò) maxRequests尤溜。

finished

我們從前面 AsyncCall 的實(shí)現(xiàn)可以看出,每次請(qǐng)求完成后汗唱,不論成功失敗宫莱,都會(huì)調(diào)用到 finished 方法通知 Dispatcher 請(qǐng)求結(jié)束:

void finished(AsyncCall call) {
    // 減少host對(duì)應(yīng)執(zhí)行中call的數(shù)量
    call.callsPerHost().decrementAndGet();
    finished(runningAsyncCalls, call);
}

它調(diào)用到了 finished 的另一個(gè)重載:

private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
        if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
        idleCallback = this.idleCallback;
    }
     
    // 嘗試執(zhí)行等待隊(duì)列中的任務(wù)
    boolean isRunning = promoteAndExecute();
    if (!isRunning && idleCallback != null) {
        idleCallback.run();
    }
}

可以看到,這里再次調(diào)用了 promoteAndExecute 方法嘗試執(zhí)行等待隊(duì)列中的任務(wù)哩罪,若當(dāng)前等待隊(duì)列中沒(méi)有需要執(zhí)行的任務(wù)授霸,說(shuō)明目前還比較空閑,沒(méi)有到達(dá)設(shè)定的 maxRequests 际插。此時(shí)會(huì)調(diào)用 idleCallback.run 執(zhí)行一些空閑 Callback

(這種設(shè)計(jì)有點(diǎn)類似 HandlerIdleHandler 機(jī)制碘耳,充分利用了一些空閑資源,值得我們學(xué)習(xí))框弛。

小結(jié)

可以看出辛辨,OkHttp 的任務(wù)的調(diào)度器的設(shè)計(jì)將請(qǐng)求分別分至了兩個(gè)隊(duì)列中,分別是等待隊(duì)列及執(zhí)行隊(duì)列瑟枫。

每次加入新的異步請(qǐng)求時(shí)斗搞,都會(huì)先將其加入等待隊(duì)列,之后遍歷等待隊(duì)列嘗試執(zhí)行等待任務(wù)慷妙。

每次加入新的同步請(qǐng)求時(shí)僻焚,都會(huì)直接將其加入執(zhí)行隊(duì)列。

而每當(dāng)一個(gè)請(qǐng)求完成時(shí)膝擂,都會(huì)通知到 Dispatcher虑啤,Dispatcher 會(huì)遍歷準(zhǔn)備隊(duì)列嘗試執(zhí)行任務(wù),若沒(méi)有執(zhí)行則說(shuō)明等待隊(duì)列是空的猿挚,則會(huì)調(diào)用 idleCallback.run 執(zhí)行一些空閑時(shí)的任務(wù)咐旧,類似 Handler 的 IdleHandler 機(jī)制。

(在多線程下載器中的任務(wù)調(diào)度器就用到了這里的 Dispatcher 的設(shè)計(jì))

響應(yīng)的獲取

從前面的同步和異步請(qǐng)求中都可以看出绩蜻,響應(yīng)的獲取的核心實(shí)現(xiàn)是 RealCall.getResponseWithInterceptorChain 方法:

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    // 初始化攔截器列表
    List<Interceptor> interceptors = new ArrayList<>();
    // 用戶自定義的 Interceptor
    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) {
        // 用戶自定義的網(wǎng)絡(luò) Interceptor
        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);
        }
    }
}

這個(gè)方法非常重要铣墨,短短幾行代碼就實(shí)現(xiàn)了對(duì)請(qǐng)求的所有處理,它體現(xiàn)了 OkHttp 中一個(gè)很重要的核心設(shè)計(jì)——攔截器機(jī)制办绝。

它首先在 interceptors 中加入了用戶自定義的攔截器伊约,之后又按順序分別加入了各種系統(tǒng)內(nèi)置的攔截器姚淆。

之后通過(guò) RealInterceptorChain 的構(gòu)造 函數(shù)構(gòu)造了一個(gè) Chain 對(duì)象,之后調(diào)用了其 proceed 方法屡律,從而得到了該請(qǐng)求的 Response腌逢。

那么這個(gè)過(guò)程中究竟是如何獲取到 Response 的呢?讓我們先理解一下 OkHttp 的攔截器機(jī)制超埋。

攔截器機(jī)制概述

OkHttp 的網(wǎng)絡(luò)請(qǐng)求的過(guò)程就是依賴于各種攔截器(Interceptor)實(shí)現(xiàn)的搏讶,我們先看看 Interceptor 的定義:

/**
 * Observes, modifies, and potentially short-circuits requests going out and the corresponding
 * responses coming back in. Typically interceptors add, remove, or transform headers on the request
 * or response.
 */
public interface Interceptor {
    Response intercept(Chain chain) throws IOException;
    
    interface Chain {
        Request request();
        Response proceed(Request request) throws IOException;
        /**
         * Returns the connection the request will be executed on. This is only available in the chains
         * of network interceptors; for application interceptors this is always null.
         */
        @Nullable
        Connection connection();
        Call call();
        int connectTimeoutMillis();
        Chain withConnectTimeout(int timeout, TimeUnit unit);
        int readTimeoutMillis();
        Chain withReadTimeout(int timeout, TimeUnit unit);
        int writeTimeoutMillis();
        Chain withWriteTimeout(int timeout, TimeUnit unit);
    }
}

Interceptor 實(shí)際上是一個(gè)接口,里面只有一個(gè)方法 intercept 以及一個(gè)接口 Chain霍殴。

Interceptor

其中媒惕,intercept 方法往往是如下的結(jié)構(gòu):

@Override 
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    // Request階段,該攔截器在Request階段負(fù)責(zé)做的事情

    // 調(diào)用RealInterceptorChain.proceed()来庭,其實(shí)是在遞歸調(diào)用下一個(gè)攔截器的intercept()方法
    response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);

    // Response階段妒蔚,完成了該攔截器在Response階段負(fù)責(zé)做的事情,然后返回到上一層的攔截器月弛。
    return response;     
}

這里先調(diào)用了 chain.request 方法獲取到了本次請(qǐng)求的 Request 對(duì)象肴盏,

之后調(diào)用了 chain.proceed 方法遞歸調(diào)用下一個(gè)攔截器的 interceptor 方法。

最后返回了 chain.proceed 方法所返回的 Response帽衙。

上面簡(jiǎn)單的三行代碼將整個(gè) intercept 過(guò)程分為了兩個(gè)階段:

  • Request 階段:執(zhí)行一些該攔截器在 Request 階段所負(fù)責(zé)的事情
  • Response 階段:完成該攔截器在 Response 階段所負(fù)責(zé)的事情

這其實(shí)是采用了一種遞歸的設(shè)計(jì)菜皂,類似我們計(jì)算機(jī)網(wǎng)絡(luò)中的分層模型,將 OkHttp 的請(qǐng)求分為了幾個(gè)階段佛寿,分別代表了不同的攔截器幌墓,不同攔截器分別會(huì)在這個(gè)遞歸的過(guò)程中有兩次對(duì)該請(qǐng)求的處理的可能,一次是在 Request 之前冀泻,一次是在 Response 之后,中間的過(guò)程中若出現(xiàn)了錯(cuò)誤蜡饵,則通過(guò)拋出異常來(lái)通知上層弹渔。

預(yù)置的 Interceptor 有如下幾種:

  • RetryAndFollowUpInterceptor:負(fù)責(zé)實(shí)現(xiàn)重定向功能
  • BridgeInterceptor:將用戶構(gòu)造的請(qǐng)求轉(zhuǎn)換為向服務(wù)器發(fā)送的請(qǐng)求,將服務(wù)器返回的響應(yīng)轉(zhuǎn)換為對(duì)用戶友好的響應(yīng)
  • CacheInterceptor:讀取緩存溯祸、更新緩存
  • ConnectInterceptor:建立與服務(wù)器的連接
  • CallServerInterceptor:從服務(wù)器讀取響應(yīng)

可以看出肢专,整個(gè)網(wǎng)絡(luò)請(qǐng)求的過(guò)程由各個(gè)攔截器互相配合從而實(shí)現(xiàn),通過(guò)這種攔截器的機(jī)制焦辅,可以很方便地調(diào)節(jié)網(wǎng)絡(luò)請(qǐng)求的過(guò)程及先后順序博杖,同時(shí)也能夠很方便地使用戶對(duì)其進(jìn)行擴(kuò)展。

其中用戶可以在兩個(gè)時(shí)機(jī)插入 Interceptor:

  • 網(wǎng)絡(luò)請(qǐng)求前后:通過(guò) OkHttpClient.addInterceptor 方法添加
  • 讀取響應(yīng)前后:通過(guò) OkHttpClient.addNetworkInterceptor 方法添加

其整體流程如圖所示:

image-20190730145213711

RealInterceptorChain

我們?cè)倏纯词侨绾瓮ㄟ^(guò) RealInterceptorChain 將整個(gè)攔截器的調(diào)用過(guò)程連接起來(lái)的筷登,我們先看看其構(gòu)造過(guò)程:

public RealInterceptorChain(List<Interceptor> interceptors, Transmitter transmitter,
                            @Nullable Exchange exchange, int index, Request request, Call call,
                            int connectTimeout, int readTimeout, int writeTimeout) {
    this.interceptors = interceptors;
    this.transmitter = transmitter;
    this.exchange = exchange;
    this.index = index;
    this.request = request;
    this.call = call;
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
    this.writeTimeout = writeTimeout;
}

這里只是一些賦值過(guò)程剃根,我們接著看到 chain.proceed 方法,看看它是如何執(zhí)行的:

public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
        throws IOException {
    // ...
    // 構(gòu)建下一個(gè)Interceptor的Chain
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
            index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    // 獲取當(dāng)前Interceptor并執(zhí)行intercept方法
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    // ...
    return response;
}

這里省略了一些異常處理前方,可以看到它首先構(gòu)造了下一個(gè)攔截器對(duì)應(yīng)的 Chain狈醉,之后獲取到了當(dāng)前的攔截器并調(diào)用了其 intercept 方法獲取其結(jié)果廉油,在 intercept 方法的參數(shù)中傳入的就是下一個(gè)攔截器對(duì)應(yīng)的 Chain

通過(guò)這種遞歸的設(shè)計(jì)苗傅,從而實(shí)現(xiàn)了從上到下抒线,再?gòu)南碌缴线@樣一個(gè)遞與歸的過(guò)程,從而十分漂亮地實(shí)現(xiàn)了 HTTP 請(qǐng)求的全過(guò)程渣慕。

這是一種類似責(zé)任鏈模式的實(shí)現(xiàn)嘶炭,這樣的實(shí)現(xiàn)在網(wǎng)絡(luò)請(qǐng)求的過(guò)程中十分常見(jiàn),也十分值得我們?nèi)W(xué)習(xí)逊桦。

小結(jié)

OkHttp 在讀取響應(yīng)的過(guò)程中采用了一種責(zé)任鏈模式旱物,預(yù)置了多個(gè)負(fù)責(zé)不同功能的攔截器,將它們通過(guò)責(zé)任鏈連接在一起卫袒,采用了一種遞歸的方式進(jìn)行調(diào)用宵呛,從而使得每一層在請(qǐng)求前和響應(yīng)后都能對(duì)本次請(qǐng)求作出不同的處理,通過(guò)各個(gè)攔截器的協(xié)調(diào)合作夕凝,最終完成了整個(gè)網(wǎng)絡(luò)請(qǐng)求的過(guò)程宝穗。

參考資料

OkHttp 3.x 源碼解析之Interceptor 攔截器

okhttp之旅(二)--請(qǐng)求與響應(yīng)流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市码秉,隨后出現(xiàn)的幾起案子逮矛,更是在濱河造成了極大的恐慌,老刑警劉巖转砖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件须鼎,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡府蔗,警方通過(guò)查閱死者的電腦和手機(jī)晋控,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)姓赤,“玉大人赡译,你說(shuō)我怎么就攤上這事〔幻” “怎么了蝌焚?”我有些...
    開(kāi)封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)誓斥。 經(jīng)常有香客問(wèn)我只洒,道長(zhǎng),這世上最難降的妖魔是什么劳坑? 我笑而不...
    開(kāi)封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任毕谴,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘析珊。我一直安慰自己羡鸥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布忠寻。 她就那樣靜靜地躺著夏醉,像睡著了一般轻抱。 火紅的嫁衣襯著肌膚如雪姐帚。 梳的紋絲不亂的頭發(fā)上器一,一...
    開(kāi)封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音纵朋,去河邊找鬼柿顶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛操软,可吹牛的內(nèi)容都是我干的嘁锯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼聂薪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼家乘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起藏澳,我...
    開(kāi)封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤仁锯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后翔悠,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體业崖,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蓄愁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了双炕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涝登,死狀恐怖雄家,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胀滚,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布乱投,位于F島的核電站咽笼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏戚炫。R本人自食惡果不足惜剑刑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧施掏,春花似錦钮惠、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至狸驳,卻和暖如春预明,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耙箍。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工撰糠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辩昆。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓阅酪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親汁针。 傳聞我的和親對(duì)象是個(gè)殘疾皇子术辐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355