okhttp網(wǎng)絡(luò)請(qǐng)求的簡(jiǎn)單使用以及源碼解析

1.okhttp的具體用法

首先需要在gradle文件中添加依賴(lài)惧眠,一個(gè)是OkHttp庫(kù)疹瘦,一個(gè)是Okio庫(kù)霜定,后者是前者通信的基礎(chǔ)。首先我們先看一下OkHttp的具體用法.

1).get請(qǐng)求


OkHttpClient client =new OkHttpClient();

Request request =new Request.Builder()

              .url("這里是請(qǐng)求的鏈接")

              .build();

Response response =client.newCall(request).execute();

String responseData =response.body().string();

首先需要?jiǎng)?chuàng)建一個(gè)OkHttpClient 對(duì)象思喊,如果需要發(fā)送http請(qǐng)求奔誓,還需要?jiǎng)?chuàng)建一個(gè)Request對(duì)象,在build()之前有很多連綴方法來(lái)豐富Request對(duì)象搔涝,在這里我們只用了url()傳入了目標(biāo)網(wǎng)絡(luò)地址。然后使用OkHttpClient 的newCall()方法來(lái)創(chuàng)建一個(gè)Call對(duì)象和措,并且調(diào)用它的execute()方法來(lái)發(fā)送請(qǐng)求并且獲取數(shù)據(jù)庄呈,Response對(duì)象就是服務(wù)器返回的數(shù)據(jù)。這里的execute()方法是同步請(qǐng)求方法派阱,如果需要使用異步請(qǐng)求需要使用Call對(duì)象的enqueue()方法 诬留,并且配合Handler進(jìn)行回調(diào)使用,實(shí)際中使用異步請(qǐng)求的情況也比較多。

2).post請(qǐng)求


ResponseBody responseBody =new FormBody.Builder()

                         .add("鍵","值")

                         .build();

Request request =new Request.Builder()

              .url("這里是請(qǐng)求的鏈接")

              .post(responseBody)

              .build();

post請(qǐng)求稍微比get請(qǐng)求復(fù)雜一點(diǎn)文兑,主要是要在ResponseBody 中添加需要傳入的參數(shù)盒刚,其余的操作就和get請(qǐng)求一樣了。

2.okhttp的請(qǐng)求過(guò)程

1.)從請(qǐng)求開(kāi)始分析

當(dāng)我們?cè)L問(wèn)網(wǎng)絡(luò)的是時(shí)候需要newOkHttpClient.newCall(request)的execute()或者enqueue()方法绿贞。當(dāng)我們調(diào)用newCall()因块,實(shí)際上是返回的一個(gè)RealCall對(duì)象,實(shí)際上調(diào)用的enqueue()和execute()也是RealCall的方法籍铁。


@Override

public Call newCall(Request request){

        return new RealCall(this, request);

}


void enqueue(Callback response Callback, boolean forWebSocket){

            synchronized(this) {

                   if(executed) throw new IllegalStateException("Already Executed");

                   executed =true; 

             }

            client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));

}

可以看到最終的請(qǐng)求是dispatcher來(lái)完成的涡上。這里面最重要的就是enqueue()方法。

2)Dispatcher調(diào)度器

Dispacther主要用于控制并發(fā)的請(qǐng)求拒名,主要維護(hù)的變量如下

/** 最大并發(fā)請(qǐng)求數(shù)*/
private int maxRequests = 64;
/** 每個(gè)主機(jī)最大請(qǐng)求數(shù)*/
private int maxRequestsPerHost = 5;
/** 消費(fèi)者線(xiàn)程池 */
private ExecutorService executorService;
/** 將要運(yùn)行的異步請(qǐng)求隊(duì)列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**正在運(yùn)行的異步請(qǐng)求隊(duì)列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在運(yùn)行的同步請(qǐng)求隊(duì)列 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

public Dispatcher(ExecutorService executorService) {
   this.executorService = executorService;
 }

 public Dispatcher() {
 }

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;
  }

Dispatcher有兩個(gè)構(gòu)造函數(shù)吩愧,可以使用自己設(shè)定的線(xiàn)程池,如果線(xiàn)程池為null的時(shí)候增显,在網(wǎng)絡(luò)請(qǐng)求前會(huì)自己創(chuàng)建一個(gè)線(xiàn)程池雁佳,這個(gè)線(xiàn)程池適合處理耗時(shí)比較少的任務(wù),因?yàn)槿蝿?wù)處理速度大于任務(wù)提交速度可以避免新的線(xiàn)程的創(chuàng)建同云,以免內(nèi)存被占滿(mǎn)糖权。

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

在調(diào)度器中,當(dāng)異步請(qǐng)求隊(duì)列中的數(shù)量小于最大請(qǐng)求數(shù)并且正運(yùn)行的主機(jī)數(shù)小于5時(shí)梢杭,則把請(qǐng)求加入線(xiàn)程池中温兼,并且執(zhí)行,否則就只是加入線(xiàn)程池武契,進(jìn)行等待募判。

AsyncCall

傳入線(xiàn)程池的AsyncCall 對(duì)象時(shí)RealCall的內(nèi)部類(lèi),他也實(shí)現(xiàn)了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 {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

在第四行中運(yùn)行了getResponseWithInterceptorChain()方法届垫,他返回了一個(gè)Response類(lèi)型的參數(shù),即是在這里向服務(wù)器發(fā)起請(qǐng)求的全释,因?yàn)樵O(shè)計(jì)到攔截器装处,這里不做討論。最終finally中代碼最后始終會(huì)運(yùn)行浸船,finished()方法如下妄迁。

void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }
...
  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

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

在第五行中,從隊(duì)列中移除了Call實(shí)例李命,第六行中因?yàn)閭魅氲膮?shù)promoteCalls為真登淘,所以會(huì)執(zhí)行 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.
    }
  }

promoteCalls主要作用就是從readyAsyncCalls線(xiàn)程池中獲取下一個(gè)請(qǐng)求,并且放在了runningAsyncCalls線(xiàn)程池中封字,并且調(diào)用了execute()方法處理黔州。

如何進(jìn)行網(wǎng)絡(luò)請(qǐng)求

上文說(shuō)到AsyncCall對(duì)象的execute()中運(yùn)行了getResponseWithInterceptorChain()方法耍鬓,進(jìn)行了網(wǎng)絡(luò)請(qǐng)求。

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>(); //這是一個(gè)List流妻,是有序的
    interceptors.addAll(client.interceptors());//首先添加的是用戶(hù)添加的全局?jǐn)r截器
    interceptors.add(retryAndFollowUpInterceptor); //錯(cuò)誤牲蜀、重定向攔截器
   //橋接攔截器,橋接應(yīng)用層與網(wǎng)絡(luò)層绅这,添加必要的頭涣达、
    interceptors.add(new BridgeInterceptor(client.cookieJar())); 
    //緩存處理,Last-Modified君躺、ETag峭判、DiskLruCache等
    interceptors.add(new CacheInterceptor(client.internalCache())); 
    //連接攔截器
    interceptors.add(new ConnectInterceptor(client));
    //從這就知道,通過(guò)okHttpClient.Builder#addNetworkInterceptor()傳進(jìn)來(lái)的攔截器只對(duì)非網(wǎng)頁(yè)的請(qǐng)求生效
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //真正訪(fǎng)問(wèn)服務(wù)器的攔截器
    interceptors.add(new CallServerInterceptor(forWebSocket));

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

這里創(chuàng)建一個(gè)關(guān)于攔截器的集合棕叫,首先將前面的client.interceptors()全部加入其中林螃,在還有創(chuàng)建 RealCall時(shí)的retryAndFollowUpInterceptor加入其中,接著還創(chuàng)建并添加了BridgeInterceptor俺泣、CacheInterceptor疗认、ConnectInterceptor、CallServerInterceptor伏钠,通過(guò)求最后RealInterceptorChain的proceed(Request)來(lái)執(zhí)行整個(gè)interceptor chain横漏,可見(jiàn)把這個(gè)攔截器鏈搞清楚,整體流程也就明朗了熟掂。這里我們只看CallServerInterceptor攔截器的功能缎浇,因?yàn)樗鼤r(shí)主要向服務(wù)器發(fā)送請(qǐng)求的攔截器。

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

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

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

    httpCodec.finishRequest();

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

    int code = response.code();
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

這部分就是從服務(wù)器發(fā)起請(qǐng)求的源碼赴肚,最終返回Response素跺,然而這里只是介紹了okhttp的如何請(qǐng)求,其實(shí)攔截器鏈才是整個(gè)框架的精髓誉券。
這里我們總結(jié)一下okhttp的請(qǐng)求過(guò)程:
newOkHttpClient.newCall(request).enqueue()方法

1.首先通過(guò)OkHttpClient創(chuàng)建一個(gè)Call對(duì)象指厌,實(shí)際上是一個(gè)RealCall
2.然后調(diào)用enqueue()方法,最終是調(diào)用client.dispatcher().enqueue()方法踊跟,主要是實(shí)現(xiàn)對(duì)任務(wù)的調(diào)度
3.最終通過(guò)攔截器實(shí)現(xiàn)對(duì)網(wǎng)絡(luò)的請(qǐng)求

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末踩验,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子商玫,更是在濱河造成了極大的恐慌箕憾,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拳昌,死亡現(xiàn)場(chǎng)離奇詭異厕九,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)地回,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)扁远,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)法希,“玉大人嵌赠,你說(shuō)我怎么就攤上這事∫幸ǎ” “怎么了细睡?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵谷羞,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我溜徙,道長(zhǎng)湃缎,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任蠢壹,我火速辦了婚禮嗓违,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘图贸。我一直安慰自己蹂季,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布疏日。 她就那樣靜靜地躺著偿洁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沟优。 梳的紋絲不亂的頭發(fā)上涕滋,一...
    開(kāi)封第一講書(shū)人閱讀 51,488評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音挠阁,去河邊找鬼宾肺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鹃唯,可吹牛的內(nèi)容都是我干的爱榕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼坡慌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼黔酥!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起洪橘,我...
    開(kāi)封第一講書(shū)人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤跪者,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后熄求,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體渣玲,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年弟晚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忘衍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逾苫。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖枚钓,靈堂內(nèi)的尸體忽然破棺而出铅搓,到底是詐尸還是另有隱情,我是刑警寧澤搀捷,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布星掰,位于F島的核電站,受9級(jí)特大地震影響嫩舟,放射性物質(zhì)發(fā)生泄漏氢烘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一家厌、第九天 我趴在偏房一處隱蔽的房頂上張望播玖。 院中可真熱鬧,春花似錦像街、人聲如沸黎棠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)脓斩。三九已至,卻和暖如春畴栖,著一層夾襖步出監(jiān)牢的瞬間随静,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工吗讶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留燎猛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓照皆,卻偏偏與公主長(zhǎng)得像重绷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子膜毁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354