okhttp流程分析

前言

今天我們來說一說okhttp框架的執(zhí)行流程,這次只說總體流程膛堤,剩下的細(xì)節(jié)我們留在后面說

okhttp的使用

在捋源碼前先回顧一下okhttp是怎么使用的

//創(chuàng)建OkHttpClient
OkHttpClient mOkHttpClient = new OkHttpClient.Builder().build();
//創(chuàng)建一個(gè)Request
Request request = new Request.Builder()
    .url("https://www.baidu.com").build();
//創(chuàng)建Call
Call call = mOkHttpClient.newCall(request);
//請(qǐng)求加入異步調(diào)度
call.enqueue(new Callback(){
    @Override
    public void onFailure(@NonNull Call call, @NonNull final IOException e){
    }

    @Override
    public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException{
        String result =  response.body().string();
    }
});

分析

1,OkHttpClient

OkHttpClient創(chuàng)建的兩種方式
1晌该,默認(rèn)使用構(gòu)造函數(shù)

public OkHttpClient() {
    this(new Builder());
  }

2肥荔,使用builder模式

public Builder() {
    dispatcher = new Dispatcher();
    protocols = DEFAULT_PROTOCOLS;
    connectionSpecs = DEFAULT_CONNECTION_SPECS;
    ...創(chuàng)建初始化參數(shù)...
}

public OkHttpClient build() {
    return new OkHttpClient(this);
}

可以看到這兩種方式最后都是將一個(gè)Builder傳遞到OkHttpClient的構(gòu)造函數(shù)里創(chuàng)建的OkHttpClient對(duì)象,一般情況下我們推薦使用第2種的builder模式气笙,因?yàn)槿绻褂玫谝环N的構(gòu)造函數(shù)方法次企,有些參數(shù)是沒法設(shè)置進(jìn)去的

2,Request

Request也是采用builder的方式,這個(gè)比較簡(jiǎn)單潜圃,就是設(shè)置一些請(qǐng)求時(shí)的參數(shù)

public Builder() {
    this.method = "GET";
    this.headers = new Headers.Builder();
}

3缸棵,Call

Call call = mOkHttpClient.newCall(request);
可以看到是使用OkHttpClient.newCall()方法創(chuàng)建的Call對(duì)象,那我們就來看一下newCall這個(gè)方法

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

這里我們看到實(shí)際創(chuàng)建的是RealCall對(duì)象谭期,newRealCall是一個(gè)靜態(tài)方法堵第,這是一個(gè)Factory模式,點(diǎn)到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.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

首先通過構(gòu)造函數(shù)創(chuàng)建了一個(gè)RealCall對(duì)象隧出,然后設(shè)置一個(gè)eventListener踏志,這個(gè)eventListener我們猜測(cè)應(yīng)該是一個(gè)請(qǐng)求過程的回調(diào),這里先不用管他胀瞪,不影響整體流程的理解针余,繼續(xù)往下看

4,將Call加入到異步調(diào)度

call.enqueue(new Callback(){...});

由于Call實(shí)際上是RealCall凄诞,我們就直接看一下RealCall的enqueue方法實(shí)現(xiàn)

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

首先是加同步鎖圆雁,判斷是否已經(jīng)執(zhí)行,如果是已經(jīng)執(zhí)行的帆谍,則拋出異常伪朽,防止重復(fù)調(diào)用
然后captureCallStackTrace(),捕獲調(diào)用過程中的堆棧信息
eventListener.callStart(this);回調(diào)通知客戶端請(qǐng)求開始了
client.dispatcher().enqueue(new AsyncCall(responseCallback));加入到異步調(diào)度中,這里要說明一下的是汛蝙,AsyncCall是RealCall的內(nèi)部類烈涮,所以可以直接取到RealCall的所有成員變量
看下client的dispatcher()方法

public Dispatcher dispatcher() {
    return dispatcher;
  }

直接返回的Dispatcher對(duì)象,那我們繼續(xù)看Dispatcher的enqueue方法

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    //加入到運(yùn)行隊(duì)列
      runningAsyncCalls.add(call);
      //加入到線程池
      executorService().execute(call);
    } else {
    //加入到等待隊(duì)列
      readyAsyncCalls.add(call);
    }
  }

這里是先判斷一下正在運(yùn)行的請(qǐng)求數(shù)是否小于maxRequests(默認(rèn)值是64)窖剑,并且請(qǐng)求當(dāng)前host的請(qǐng)求數(shù)量是否小于maxRequestsPerHost(默認(rèn)值5)坚洽,如果條件都滿足則加入到運(yùn)行隊(duì)列并加入到線程池中開始執(zhí)行,如果不滿足則加入到等待隊(duì)列中
再看一下runningCallsForHost是如何獲取當(dāng)前call的host的請(qǐng)求數(shù)量的

/** Returns the number of running calls that share a host with {@code call}. */
  private int runningCallsForHost(AsyncCall call) {
    int result = 0;
    for (AsyncCall c : runningAsyncCalls) {
      if (c.get().forWebSocket) continue;
      if (c.host().equals(call.host())) result++;
    }
    return result;
  }

可以看到西土,整個(gè)過程就是遍歷runningAsyncCalls讶舰,判斷與當(dāng)前的host相等就計(jì)數(shù)器加一,然后返回;
好绘雁,那么重點(diǎn)來了,把call加入到線程池后援所,他是如何運(yùn)行的呢庐舟?由于是加入到線程池,那么AsyncCall必定是一個(gè)Runnable的實(shí)現(xiàn)類住拭,而且執(zhí)行代碼會(huì)在run方法里挪略,我們看一下

final class AsyncCall extends NamedRunnable {
......
}

這里看到AsyncCall是繼承自NamedRunnable,再看一下NamedRunnable這個(gè)類

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

我們看到這里的run方法首先是設(shè)置了一個(gè)當(dāng)前線程的name滔岳,然后調(diào)用execute方法杠娱,最后再把原來的線程name設(shè)置回來,run主要執(zhí)行的就是這個(gè)execute方法了谱煤,execute是的抽象方法摊求,我們?cè)倩氐紸syncCall看這個(gè)execute方法

@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 {
        client.dispatcher().finished(this);
      }
    }
  }

這個(gè)balabala一大堆,但如果簡(jiǎn)化一下看的就明白了

@Override 
protected void execute() {
    //使用攔截器執(zhí)行網(wǎng)絡(luò)請(qǐng)求刘离,這是okhttp的特色
    Response response = getResponseWithInterceptorChain();
    //回調(diào)成功或失敗
    responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
    responseCallback.onResponse(RealCall.this, response);
    //調(diào)用Dispatcher的finish方法室叉,將當(dāng)前call從運(yùn)行隊(duì)列中移除,并將等待隊(duì)列中的call加入到運(yùn)行隊(duì)列
    client.dispatcher().finished(this);
}

主要就是三步硫惕,使用攔截器執(zhí)行網(wǎng)絡(luò)請(qǐng)求茧痕,回調(diào)結(jié)果,然后finished恼除,這里需要重點(diǎn)看一的是getResponseWithInterceptorChain這個(gè)方法

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //加入應(yīng)用攔截器踪旷,就是用戶添加的攔截器
    interceptors.addAll(client.interceptors());
    //加入網(wǎng)絡(luò)攔截器
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
    //加入用戶自定義的網(wǎng)絡(luò)攔截器
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

這個(gè)方法實(shí)現(xiàn)的就是將用戶添加的攔截器和框架自身的網(wǎng)絡(luò)攔截器寫進(jìn)interceptors集合,然后通過RealInterceptorChain去遞歸調(diào)用所有的攔截器豁辉,最后將結(jié)果返回
最后我們?cè)倩剡^頭來看一下 client.dispatcher().finished(this); 這個(gè)方法都做了哪些操作

/** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }
//實(shí)際調(diào)用的是這個(gè)方法
  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
    //把當(dāng)前的call從隊(duì)列中移除令野,calls就是runningAsyncCalls
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      //這個(gè)promoteCalls是從上個(gè)函數(shù)傳進(jìn)來的true
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

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

這個(gè)finished方法將當(dāng)前的call從運(yùn)行隊(duì)列runningAsyncCalls中移除,然后調(diào)用promoteCalls()方法秋忙,然后是一個(gè)閑置狀態(tài)的回調(diào)彩掐,這里我們主要看一下promoteCalls()方法

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; // Reached max capacity.
    }
  }

前面的兩個(gè)判斷可以忽略掉,看下這個(gè)for循環(huán)灰追,通過迭代器遍歷等待隊(duì)列readyAsyncCalls堵幽,if (runningCallsForHost(call) < maxRequestsPerHost)判斷當(dāng)前host正在的call是否小于最大值,如果是true弹澎,則 i.remove();從等待隊(duì)列中移除朴下,runningAsyncCalls.add(call);executorService().execute(call);加入到運(yùn)行隊(duì)列并加入到線程池中執(zhí),最后一個(gè)判斷是如果運(yùn)行隊(duì)列已滿則返回
這個(gè)方法的作用就是將等待隊(duì)列readyAsyncCalls中的Call加入到運(yùn)行隊(duì)列runningAsyncCalls中

總結(jié)

okhttp的請(qǐng)求過程就是將請(qǐng)求Call加入到線程池中執(zhí)行苦蒿,并同時(shí)加入到隊(duì)列中殴胧;執(zhí)行的過程就是通過一系列的Interceptor最后得到Response返回給客戶端;同步方式與異步方式類似,只是不需要線程池了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末团滥,一起剝皮案震驚了整個(gè)濱河市竿屹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灸姊,老刑警劉巖拱燃,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異力惯,居然都是意外死亡碗誉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門父晶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哮缺,“玉大人,你說我怎么就攤上這事甲喝〕⑽” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵埠胖,是天一觀的道長茎匠。 經(jīng)常有香客問我,道長押袍,這世上最難降的妖魔是什么诵冒? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮谊惭,結(jié)果婚禮上汽馋,老公的妹妹穿的比我還像新娘。我一直安慰自己圈盔,他們只是感情好豹芯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著驱敲,像睡著了一般铁蹈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上众眨,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天握牧,我揣著相機(jī)與錄音,去河邊找鬼娩梨。 笑死沿腰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的狈定。 我是一名探鬼主播颂龙,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼习蓬,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了措嵌?” 一聲冷哼從身側(cè)響起躲叼,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎企巢,沒想到半個(gè)月后押赊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡包斑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涕俗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罗丰。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖再姑,靈堂內(nèi)的尸體忽然破棺而出萌抵,到底是詐尸還是另有隱情,我是刑警寧澤元镀,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布绍填,位于F島的核電站,受9級(jí)特大地震影響栖疑,放射性物質(zhì)發(fā)生泄漏讨永。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一遇革、第九天 我趴在偏房一處隱蔽的房頂上張望卿闹。 院中可真熱鬧,春花似錦萝快、人聲如沸锻霎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旋恼。三九已至,卻和暖如春奄容,著一層夾襖步出監(jiān)牢的瞬間冰更,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工昂勒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冬殃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓叁怪,卻偏偏與公主長得像审葬,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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

  • 昨天跟好久未聯(lián)系的朋友聊天涣觉,才得知她父親上月剛剛?cè)ナ赖南ⅰ?上一秒還在說正事痴荐,我還發(fā)了個(gè)可愛的表情。 下一秒便收...
    翯翯勒勒閱讀 570評(píng)論 0 2
  • 當(dāng)我查到錄取結(jié)果時(shí)官册,腿都有點(diǎn)發(fā)軟了生兆,心里無數(shù)個(gè)聲音在告訴自己,這不是真的…… 我知道這個(gè)結(jié)果對(duì)于家里來說是個(gè)不...
    生筆閱讀 159評(píng)論 0 0
  • 2018年10月28日周日 重塑身心第14天 不知不覺已經(jīng)堅(jiān)持昆達(dá)里尼瑜伽半個(gè)月了膝宁,算上最初學(xué)習(xí)的兩天鸦难,就是16天...
    韓瑞琳Rachel閱讀 324評(píng)論 0 0
  • 排隊(duì)時(shí)間太長,背景音樂太響员淫,公司能接受的支付卡有限……可是合蔽,消除所有這些不滿之處就真的會(huì)讓客戶滿意嗎?根據(jù)研究介返,這...
    8c4a72d1331b閱讀 100評(píng)論 0 0