OKHttp3原理分析

寫(xiě)在前面

作為一名android開(kāi)發(fā)者迫吐,要時(shí)時(shí)刻刻跟隨技術(shù)的發(fā)展潮流宰缤,OKHttp作為當(dāng)下最流行的網(wǎng)絡(luò)請(qǐng)求框架我們不得不重視痊剖,它的原理也幾乎是面試時(shí)必問(wèn)的問(wèn)題告丢,故而對(duì)其進(jìn)行學(xué)習(xí)并紀(jì)錄之枪蘑。

幾個(gè)類

  • OKHttpClient
    okhttp3在項(xiàng)目中發(fā)起請(qǐng)求的代碼如下:
    okHttpClient.newCall(request).execute();
    OKHttpClient 類中組合了很多的類對(duì)象,并且繼承了Call.Factory岖免,提供的方法只有一個(gè):newCall岳颇,返回的是一個(gè)Call對(duì)象(實(shí)際是RealCall是Call的實(shí)現(xiàn)類)。
@Override 
public Call newCall(Request request) {
    return new RealCall(this, request);
  }

使用okHttpClient最好創(chuàng)建一個(gè)單例颅湘,因?yàn)槊恳粋€(gè)client都有自己的一個(gè)連接池connection pool和線程池thread pools话侧。重用這些連接池和線程池可以減少延遲和節(jié)約內(nèi)存。
okHttpClient使用了builder模式

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

可以看到OKHttpClient中包含的所有字段闯参。

  • Dispatcher
    Dispatcher我們可以理解為一個(gè)執(zhí)行策略(官方這樣說(shuō)的:Policy on when async requests are executed.)瞻鹏,當(dāng)我們調(diào)用newCall時(shí)悲立,它不斷的從RequestQueue中取出請(qǐng)求(Call),該引擎有同步和異步請(qǐng)求新博,同步請(qǐng)求通過(guò)Call.execute()直接返 回當(dāng)前的Response薪夕,而異步請(qǐng)求會(huì)把當(dāng)前的請(qǐng)求Call.enqueue添加(AsyncCall)到請(qǐng)求隊(duì)列中,并通過(guò)回調(diào)(Callback) 的方式來(lái)獲取最后結(jié)果赫悄。
synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

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

注意到原献,這里enqueue方法中有一個(gè)判斷:如果當(dāng)前運(yùn)行的異步請(qǐng)求隊(duì)列長(zhǎng)度小于最大請(qǐng)求數(shù),也就是64,并且主機(jī)的請(qǐng)求數(shù)小于每個(gè)主機(jī)的請(qǐng)求數(shù)也就是5,則把當(dāng)前請(qǐng)求添加到 運(yùn)行隊(duì)列埂淮,接著交給線程池ExecutorService處理姑隅,否則則放置到readAsyncCall進(jìn)行緩存,等待執(zhí)行倔撞。

  • Call
public interface Call {

  Request request();

  Response execute() throws IOException;

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  interface Factory {
    Call newCall(Request request);
  }
}

Call是一個(gè)接口類讲仰,定義了Http請(qǐng)求的方法,并提供了一個(gè)內(nèi)部接口Factory痪蝇,OkHttpClient即實(shí)現(xiàn)了該接口叮盘。

  • RealCall
    RealCall是Call的實(shí)現(xiàn)類,里面最主要的兩個(gè)方法是execute和enqueue霹俺。
final class RealCall implements Call {
   ...
   @Override
    public Response execute() throws IOException {
       synchronized (this) {
         if (executed) throw new IllegalStateException("Already Executed");
         executed = true;
       }
       try {
         client.dispatcher().executed(this);
         Response result = getResponseWithInterceptorChain();
         if (result == null) throw new IOException("Canceled");
         return result;
       } finally {
         client.dispatcher().finished(this);
       }
     }
   ...
   @Override 
   public void enqueue(Callback responseCallback) {
       synchronized (this) {
         if (executed) throw new IllegalStateException("Already Executed");
         executed = true;
       }
       client.dispatcher().enqueue(new AsyncCall(responseCallback));
     }
   ...
}

可以看到該類中的execute和enqueue都調(diào)用了dispatcher中的executed和enqueue(代碼前面已貼)柔吼。
上面的代碼中也可以解釋我們前面所說(shuō)的:“同步請(qǐng)求通過(guò)Call.execute()直接返 回當(dāng)前的Response,而異步請(qǐng)求會(huì)把當(dāng)前的請(qǐng)求Call.enqueue添加(AsyncCall)到請(qǐng)求隊(duì)列中丙唧,并通過(guò)回調(diào)(Callback) 的方式來(lái)獲取最后結(jié)果愈魏。”
execute通過(guò)getResponseWithInterceptorChain獲取返回Response想际。
同步方法上面的代碼已經(jīng)足夠了培漏, 這里重點(diǎn)說(shuō)一下異步請(qǐng)求如何獲得返回的結(jié)果:
再回到Dispatcher類,enqueue異步方法中執(zhí)行了executorService().execute(call)胡本,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;
  }

可以看到上面的代碼中用到了線程池牌柄,這也就意味著后半句的executor(call)必然涉及到了多線程的問(wèn)題,我們來(lái)看代碼:

public interface Executor {
    void execute(Runnable command);
}

可以看到execute的參數(shù)是一個(gè)Runnable侧甫,這也意味著AsyncCall必然是一個(gè)Runnable的子類珊佣。下面來(lái)看AsyncCall的源碼:

  • AsyncCall
final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    private AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl().toString());
      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 {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }
   ...
}

從上面的代碼中我們可以看到AysncCall繼承自NamedRunnable抽象類,并且會(huì)執(zhí)行execute方法披粟,而execute方法中我們又看到了熟悉的代碼:
Response response = getResponseWithInterceptorChain();
可見(jiàn)返回結(jié)果也是通過(guò)調(diào)用這個(gè)方法得到的咒锻,只不過(guò)與同步相比中間增加了一些過(guò)程,并且請(qǐng)求結(jié)果是通過(guò)responseCallBack返回守屉。
我們來(lái)看一下這個(gè)方法的代碼:

private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(
        retryAndFollowUpInterceptor.isForWebSocket()));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

可以看到惑艇,這個(gè)方法中主要是用到了Interceptor類。

  • Interceptor
public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    Connection connection();
  }
}

Interceptor接口代碼很少,但是滨巴;卻是okhttp中非常核心的類思灌,它把實(shí)際的網(wǎng)絡(luò)請(qǐng)求、緩存恭取、透明壓縮等功能都統(tǒng)一了起來(lái)泰偿,每一個(gè)功能都是一個(gè)Interceptor,它們?cè)龠B成一個(gè)Interceptor.Chain秽荤,環(huán)環(huán)相扣,完成一次網(wǎng)絡(luò)請(qǐng)求柠横。
分析getResponseWithInterceptorChain()方法中用到的interceptor:
1窃款,通過(guò)client設(shè)置的interceptors(即builder.addInterceptor())
2,RetryAndFollowUpInterceptor牍氛,負(fù)責(zé)重試和重定向
3晨继,BridgeInterceptor,首先將應(yīng)用層的數(shù)據(jù)類型轉(zhuǎn)換為網(wǎng)絡(luò)調(diào)用層的數(shù)據(jù)類型搬俊,然后將網(wǎng)絡(luò)層返回的數(shù)據(jù)類型轉(zhuǎn)換為應(yīng)用層的數(shù)據(jù)類型
4紊扬,CacheInterceptor,負(fù)責(zé)讀取緩存唉擂,更新緩存
5餐屎,ConnectInterceptor,負(fù)責(zé)和服務(wù)器建立起鏈接
6玩祟,networkInterceptors腹缩,client設(shè)置的networkInterceptor
7,CallServerInterceptor空扎,負(fù)責(zé)向服務(wù)器發(fā)送請(qǐng)求數(shù)據(jù)藏鹊、從服務(wù)器讀取響應(yīng)數(shù)據(jù)
最后一個(gè)interceptor是負(fù)責(zé)跟服務(wù)器通訊的,其他的interceptor配置都要在此之前转锈。

@Override public Response intercept(Chain chain) throws IOException {
    HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
    StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
    Request request = chain.request();

    long sentRequestMillis = System.currentTimeMillis();
    httpStream.writeRequestHeaders(request);

    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
      BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
      request.body().writeTo(bufferedRequestBody);
      bufferedRequestBody.close();
    }

    httpStream.finishRequest();

    Response response = httpStream.readResponseHeaders()
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    if (!forWebSocket || response.code() != 101) {
      response = response.newBuilder()
          .body(httpStream.openResponseBody(response))
          .build();
    }
    ...
    return response;
  }

Http請(qǐng)求流程圖

okhttp_full_process.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盘寡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子撮慨,更是在濱河造成了極大的恐慌竿痰,老刑警劉巖砌溺,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菇曲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡抚吠,警方通過(guò)查閱死者的電腦和手機(jī)常潮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)楷力,“玉大人喊式,你說(shuō)我怎么就攤上這事孵户。” “怎么了岔留?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵夏哭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我献联,道長(zhǎng)竖配,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任里逆,我火速辦了婚禮进胯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘原押。我一直安慰自己胁镐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布诸衔。 她就那樣靜靜地躺著盯漂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪笨农。 梳的紋絲不亂的頭發(fā)上就缆,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音谒亦,去河邊找鬼违崇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诊霹,可吹牛的內(nèi)容都是我干的羞延。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼脾还,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伴箩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起鄙漏,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嗤谚,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后怔蚌,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體巩步,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年桦踊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了椅野。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖竟闪,靈堂內(nèi)的尸體忽然破棺而出离福,到底是詐尸還是另有隱情,我是刑警寧澤炼蛤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布妖爷,位于F島的核電站,受9級(jí)特大地震影響理朋,放射性物質(zhì)發(fā)生泄漏絮识。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一嗽上、第九天 我趴在偏房一處隱蔽的房頂上張望次舌。 院中可真熱鬧,春花似錦炸裆、人聲如沸垃它。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至洛史,卻和暖如春惯殊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背也殖。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工土思, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人忆嗜。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓己儒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親捆毫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闪湾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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

  • OkHttp解析系列 OkHttp解析(一)從用法看清原理OkHttp解析(二)網(wǎng)絡(luò)連接OkHttp解析(三)關(guān)于...
    Hohohong閱讀 20,974評(píng)論 4 58
  • OkHttp源碼的samples的簡(jiǎn)單使用的示例: public static void main(String....
    _warren閱讀 749評(píng)論 0 1
  • 關(guān)于okhttp是一款優(yōu)秀的網(wǎng)絡(luò)請(qǐng)求框架,關(guān)于它的源碼分析文章有很多绩卤,這里分享我在學(xué)習(xí)過(guò)程中讀到的感覺(jué)比較好的文章...
    蕉下孤客閱讀 3,602評(píng)論 2 38
  • 今天上午又陷入到了老的模式中途样,因?yàn)闆](méi)有人安排我的工作,我一會(huì)兒干干這個(gè)濒憋,一會(huì)兒想想那個(gè)何暇。天,我來(lái)這里是來(lái)提升講課技...
    cllian119閱讀 142評(píng)論 0 0
  • ————個(gè)人情況 由于本人體質(zhì)氣虛畏寒,在初中的時(shí)候過(guò)度節(jié)食,從而就開(kāi)始了長(zhǎng)達(dá)六年的便秘遏插。周?chē)呐笥褞缀醵际且惶毂?..
    zgfqsd閱讀 1,619評(píng)論 0 1