Retrofit源碼之OKHttpCall

之前在Retrofit源碼初探一文中我們提出了三個問題:

  • 什么時候開始將注解中參數(shù)拼裝成http請求的信息的移怯?
  • 如何產(chǎn)生發(fā)起http請求對象的蚕捉?
  • 如何將對象轉(zhuǎn)換成我們在接口中指定的返回值的亿遂?
    其中第一個問題前幾篇文章已經(jīng)做了解答,今天我們探究下第二個問題拭抬。
    之前也分析過占业,具體生成這個請求對象的是這句代碼:
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);

代碼很簡單,那我們就來探究下這個OkHttpCall能干什么:

final class OkHttpCall<T> implements Call<T> {

可以看到其實主要實現(xiàn)了一個接口赦肋,所以我們看下這個接口都有哪些方法:

public interface Call<T> extends Cloneable {
 
  Response<T> execute() throws IOException;

  void enqueue(Callback<T> callback);

  boolean isExecuted();

  void cancel();

  boolean isCanceled();

  Call<T> clone();

  /** The original HTTP request. */
  Request request();
}

看到這幾個方法有沒有很熟悉块攒,沒錯,幾乎和Okhttp的Call方法一模一樣佃乘,我們看下okhttp的call接口:

public interface Call extends Cloneable {
    Request request();

    Response execute() throws IOException;

    void enqueue(Callback var1);

    void cancel();

    boolean isExecuted();

    boolean isCanceled();

    Call clone();

    public interface Factory {
        Call newCall(Request var1);
    }
}

從這里我們猜測囱井,Retrofit的OkHttpCall其實就是對OkHttp的call的一種包裝,下面我們詳細(xì)探究下每種方法趣避,看是如何分別調(diào)用OkHttp的call中的方法的庞呕,有沒有做什么特殊處理。

之前有提過看源碼之前要帶著問題去看鹅巍,那么對于這個OkHttpCall我們想知道什么千扶?之前提到過這是對OkHttp的okhttp3.Call的一個封裝,那么每個方法必然會調(diào)用到okhttp3.Call對應(yīng)的方法骆捧,所以我們提出兩個問題:

  • 這個類中okhttp3.Call對象是怎么生成的澎羞?
  • 調(diào)用okhttp3.Call中對應(yīng)的方法時有沒有做什么特殊操作?

這兩個問題在每個主要方法中都能得到答案敛苇。

request()方法

  @Override public synchronized Request request() {
    okhttp3.Call call = rawCall;
    if (call != null) {
      return call.request();
    }
    if (creationFailure != null) {
      if (creationFailure instanceof IOException) {
        throw new RuntimeException("Unable to create request.", creationFailure);
      } else if (creationFailure instanceof RuntimeException) {
        throw (RuntimeException) creationFailure;
      } else {
        throw (Error) creationFailure;
      }
    }
    try {
      return (rawCall = createRawCall()).request();
    } catch (RuntimeException | Error e) {
      throwIfFatal(e); // Do not assign a fatal error to creationFailure.
      creationFailure = e;
      throw e;
    } catch (IOException e) {
      creationFailure = e;
      throw new RuntimeException("Unable to create request.", e);
    }
  }

可以看到妆绞,大致邏輯就是如果okhttp3.Call已經(jīng)被實例化了直接調(diào)用它的request()方法顺呕,如果沒有的話,會調(diào)用createRawCall()方法先實例化括饶,然后再調(diào)用request方法株茶。
所以想要解答okhttp3.Call是怎么生成的,就來看看這個createRawCall()方法:

  private okhttp3.Call createRawCall() throws IOException {
    okhttp3.Call call = serviceMethod.toCall(args);
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
  }

可以看到核心方法還是ServiceMethod中的toCall方法來生成的图焰,這里提供了參數(shù)而已,繼續(xù)跟進去:

okhttp3.Call toCall(@Nullable Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);

    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

    int argumentCount = args != null ? args.length : 0;
    if (argumentCount != handlers.length) {
      throw new IllegalArgumentException("Argument count (" + argumentCount
          + ") doesn't match expected count (" + handlers.length + ")");
    }

    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }

    return callFactory.newCall(requestBuilder.build());
  }

這個方法最終其實就是調(diào)用okhttp3.Call中的這個方法:

    public interface Factory {
        Call newCall(Request var1);
    }

至于怎么根據(jù)Request生成Call是OkHttp干的技羔,在ServiceMethod中的toCall方法僵闯,我們要做的就是用已有信息生成一個OkHttp的Request來,如何生成這個Request?這里利用了一個RequesetBuilder藤滥。
第一:處理方法級別的注解的信息
利用httpMethod,baseUrl,relativeUrl等直接new了一個RequestBuilder出來鳖粟,這些信息都是從方法級別的注解中解析出來的。
第二:處理參數(shù)級別的注解信息
之前在生成ServiceMethod對象時拙绊,利用參數(shù)級別的注解生成了一個ParameterHandler數(shù)組向图,每個Handler都有一個apply方法,將參數(shù)信息設(shè)置到一個RequestBuilder中标沪,這個apply方法就是在這里調(diào)用的榄攀。
經(jīng)過上面兩部,一個包含了http請求完整信息的RequesetBuilder就生成了谨娜,最后build下生成一個Request傳到newCall方法中航攒,則一個okhttp3.Call對象就生成了磺陡。

整個request()方法分析完了趴梢,做的事很簡單,有okhttp3.Call對象就直接調(diào)用它的request()方法币他,沒有就生成一個再調(diào)用坞靶,但大家注意到?jīng)]有,他的代碼設(shè)計安排很奇怪蝴悉。如果是我來寫這個方法彰阴,我可能會這樣寫:

  public synchronized Request request1() {
    okhttp3.Call call = rawCall;
    if (call != null) {
      return call.request();
    }else{
      try {
        return (rawCall = createRawCall()).request();
      } catch (RuntimeException | Error e) {
        throwIfFatal(e); // Do not assign a fatal error to creationFailure.
        throw e;
      } catch (IOException e) {
        throw new RuntimeException("Unable to create request.", e);
      }
    }
  }

可以看到,和我自己的代碼相比拍冠,原代碼多了一個記錄createRawCall()的異常的成員變量尿这,這是處于效率考慮。由于我們的okhtt3.Call對象是延遲加載的庆杜,就是說在調(diào)用request方法時射众,其他的方法中有可能已經(jīng)調(diào)用過createRawCall()方法,并由于某種原因失敗了晃财,我們將這個失敗的異常記錄下來叨橱,在調(diào)用createRawCall()方法之前做一次判斷,如果已有異常就不需要調(diào)用createRawCall()方法了,提高了效率罗洗。

enque()

整個enque()方法的核心必然是調(diào)用okhttp3.Call的enque方法愉舔,我們重點關(guān)注調(diào)用之前有做什么,調(diào)用之后做了什么:

call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
          throws IOException {
        Response<T> response;
        try {
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          callFailure(e);
          return;
        }
        callSuccess(response);
      }

      @Override public void onFailure(okhttp3.Call call, IOException e) {
        callFailure(e);
      }

在調(diào)用之前其實沒做什么伙菜,和request()方法差不多轩缤,做了下提前判斷而已,所以這里可以直接看代碼贩绕,核心就是調(diào)用了parseResponse()方法將返回值轉(zhuǎn)成了Retrofit的Response對象典奉,然后調(diào)用了callSuccess()而已,所以我們跟進去:

  Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ResponseBody rawBody = rawResponse.body();

    // Remove the body's source (the only stateful object) so we can pass the response along.
    rawResponse = rawResponse.newBuilder()
        .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
        .build();

    int code = rawResponse.code();
    if (code < 200 || code >= 300) {
      try {
        // Buffer the entire body to avoid future I/O.
        ResponseBody bufferedBody = Utils.buffer(rawBody);
        return Response.error(bufferedBody, rawResponse);
      } finally {
        rawBody.close();
      }
    }

    if (code == 204 || code == 205) {
      rawBody.close();
      return Response.success(null, rawResponse);
    }

    ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
    try {
      T body = serviceMethod.toResponse(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
      // If the underlying source threw an exception, propagate that rather than indicating it was
      // a runtime exception.
      catchingBody.throwIfCaught();
      throw e;
    }
  }

這里邏輯很簡單丧叽,根據(jù)不同的http狀態(tài)碼返回對應(yīng)的Response對象卫玖,這里有一點,當(dāng)狀態(tài)碼正常時踊淳,這里會利用一個converter將Body對象轉(zhuǎn)成自己想要的假瞬,比如轉(zhuǎn)成json等,具體處理是在serviceMethod.toResponse()中進行的迂尝。

日常偷懶環(huán)節(jié)

好了脱茉,關(guān)鍵時刻來了,分析了這兩個方法后垄开,OkHttpCall中的主要方法應(yīng)該都講到了琴许,剩下的一些方法基本和上面兩個差不多,大家對著來就行了溉躲!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末榜田,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子锻梳,更是在濱河造成了極大的恐慌箭券,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疑枯,死亡現(xiàn)場離奇詭異辩块,居然都是意外死亡,警方通過查閱死者的電腦和手機荆永,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門废亭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人具钥,你說我怎么就攤上這事豆村。” “怎么了氓拼?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵你画,是天一觀的道長抵碟。 經(jīng)常有香客問我,道長坏匪,這世上最難降的妖魔是什么拟逮? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮适滓,結(jié)果婚禮上敦迄,老公的妹妹穿的比我還像新娘。我一直安慰自己凭迹,他們只是感情好罚屋,可當(dāng)我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嗅绸,像睡著了一般脾猛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鱼鸠,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天猛拴,我揣著相機與錄音,去河邊找鬼蚀狰。 笑死愉昆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的麻蹋。 我是一名探鬼主播跛溉,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扮授!你這毒婦竟也來了芳室?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤糙箍,失蹤者是張志新(化名)和其女友劉穎渤愁,沒想到半個月后牵祟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體深夯,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年诺苹,在試婚紗的時候發(fā)現(xiàn)自己被綠了咕晋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡收奔,死狀恐怖掌呜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坪哄,我是刑警寧澤质蕉,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布势篡,位于F島的核電站,受9級特大地震影響模暗,放射性物質(zhì)發(fā)生泄漏禁悠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一兑宇、第九天 我趴在偏房一處隱蔽的房頂上張望碍侦。 院中可真熱鬧,春花似錦隶糕、人聲如沸瓷产。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽濒旦。三九已至,卻和暖如春再登,著一層夾襖步出監(jiān)牢的瞬間疤估,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工霎冯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铃拇,地道東北人。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓沈撞,卻偏偏與公主長得像慷荔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缠俺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,562評論 2 349

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