Retrofit2 源碼解析

Retrofit2 源碼解析

1. Retrofit是什么

來自Retrofit官網(wǎng)的介紹:

A type-safe HTTP client for Android and Java

一個基于OkHttp的RESTFUL Api請求工具

2. Retrofit的用法

  • 創(chuàng)建Retrofit對象
public static final String API_URL = "https://api.github.com";

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(API_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build();
  • api新建一個Java接口,用Java注解來描述這個api
public interface GitHubApiService {
@GET("/users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
// you can add some other meathod
}
  • 這個retrofit對象創(chuàng)建一個GitHubService對象:
GitHubApiService gitHubApiService=retrofit.create(GitHubApiService.class);//獲取API接口的實(shí)現(xiàn)類的實(shí)例對象

Call<List<Repo>> call = gitHubApiService.listRepos("octocat");

  • 異步回調(diào)結(jié)果
// 請求數(shù)據(jù)黎泣,并且處理response
call.enqueue(new Callback<List<Repo>>() {
    @Override
    public void onResponse(Response<List<Repo>> list) {
        Log.d("list: " + list);
    }
    @Override
    public void onFailure(Throwable t) {
    }
});
  • 同步回調(diào)結(jié)果
 List<Repo> list = call.execute();

3. Retrofit的原理

Retrofit整個工作流程:將java接口窖壕,轉(zhuǎn)換成一個個Http請求隶症,交由Okhttp進(jìn)行發(fā)送阀蒂。

Retrofit庫依賴了Okhttp庫和okio庫,核心庫分為Http包其他核心類驮捍。

Http包里面包含各種定義的注解危虱,幫忙開發(fā)者定義請求方式和請求參數(shù)赫舒。

如何將一個請求放到Okhttp里面去?

答案是: Retrofit利用了java的動態(tài)代理技術(shù)悍及。

動態(tài)代理代碼如下:

/** Create an implementation of the API defined by the {@code service} interface. */
public <T> T create(final Class<T> service) {
  Utils.validateServiceInterface(service);
  if (validateEagerly) {
     eagerlyValidateMethods(service);
  }
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
    new InvocationHandler() {
      private final Platform platform = Platform.get();

      @Override public Object invoke(Object proxy, Method method, Object... args)
          throws Throwable {
        // If the method is a method from Object then defer to normal invocation.
        if (method.getDeclaringClass() == Object.class) {
          return method.invoke(this, args);
        }
        if (platform.isDefaultMethod(method)) {
          return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        ServiceMethod serviceMethod = loadServiceMethod(method);
        OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
        return serviceMethod.callAdapter.adapt(okHttpCall);
      }
    });

首先看動態(tài)代理方法,這里是j接口動態(tài)代理的標(biāo)準(zhǔn)寫法

 Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
    new InvocationHandler(){});

InvocationHandler類中的插入方法

      private final Platform platform = Platform.get();

      @Override public Object invoke(Object proxy, Method method, Object... args)
          throws Throwable {
        // If the method is a method from Object then defer to normal invocation.
        if (method.getDeclaringClass() == Object.class) {
          return method.invoke(this, args);
        }
        if (platform.isDefaultMethod(method)) {
          return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        ServiceMethod serviceMethod = loadServiceMethod(method);
        OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
        return serviceMethod.callAdapter.adapt(okHttpCall);
      }

api對象其實(shí)是一個動態(tài)代理對象,類型是接口類型接癌,真正的對象是被動態(tài)代理攔截心赶,然后調(diào)用Proxy.newProxyInstance方法中的InvocationHandler對象。

其中invoke方法:

  • Object proxy: 代理對象

  • Method method:調(diào)用的方法

  • Object... args:方法的參數(shù)

然后Retrofit就會用Java反射獲取到方法的注解信息扔涧,配合args參數(shù)园担,創(chuàng)建一個ServiceMethod對象届谈。

ServiceMethod就像是一個處理器,傳入Retrofit對象和Method對象弯汰,調(diào)用各個接口和解析器艰山,最終生成一個Request,包含api 的域名咏闪、path曙搬、http請求方法、請求頭鸽嫂、是否有body纵装、是否是multipart等等。最后返回一個Call對象据某,Retrofit2中Call接口的默認(rèn)實(shí)現(xiàn)是OkHttpCall橡娄,它默認(rèn)使用OkHttp3作為底層http請求client

翻譯請求方式和參數(shù),同時構(gòu)建出ServiceMethod對象

 public ServiceMethod build() {
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      if (responseType == Response.class || responseType == okhttp3.Response.class) {
        throw methodError("'"
            + Utils.getRawType(responseType).getName()
            + "' is not a valid response body type. Did you mean ResponseBody?");
      }
      responseConverter = createResponseConverter();

      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }

      if (httpMethod == null) {
        throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
      }

      if (!hasBody) {
        if (isMultipart) {
          throw methodError(
              "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
        }
        if (isFormEncoded) {
          throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
              + "request body (e.g., @POST).");
        }
      }

      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        Type parameterType = parameterTypes[p];
        if (Utils.hasUnresolvableType(parameterType)) {
          throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
              parameterType);
        }

        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
        if (parameterAnnotations == null) {
          throw parameterError(p, "No Retrofit annotation found.");
        }

        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }

      if (relativeUrl == null && !gotUrl) {
        throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
      }
      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
        throw methodError("Non-body HTTP method cannot contain @Body.");
      }
      if (isFormEncoded && !gotField) {
        throw methodError("Form-encoded method must contain at least one @Field.");
      }
      if (isMultipart && !gotPart) {
        throw methodError("Multipart method must contain at least one @Part.");
      }

      return new ServiceMethod<>(this);
    }

總結(jié)來說:使用Java動態(tài)代理的目的就要攔截被調(diào)用的Java方法癣籽,然后解析這個Java方法的注解挽唉,最后生成Request由OkHttp發(fā)送。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末筷狼,一起剝皮案震驚了整個濱河市瓶籽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌埂材,老刑警劉巖塑顺,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異俏险,居然都是意外死亡严拒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門寡喝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糙俗,“玉大人勒奇,你說我怎么就攤上這事预鬓。” “怎么了赊颠?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵格二,是天一觀的道長。 經(jīng)常有香客問我竣蹦,道長顶猜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任痘括,我火速辦了婚禮长窄,結(jié)果婚禮上滔吠,老公的妹妹穿的比我還像新娘。我一直安慰自己挠日,他們只是感情好疮绷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嚣潜,像睡著了一般冬骚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上懂算,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天只冻,我揣著相機(jī)與錄音,去河邊找鬼计技。 笑死喜德,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垮媒。 我是一名探鬼主播住诸,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼涣澡!你這毒婦竟也來了贱呐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤入桂,失蹤者是張志新(化名)和其女友劉穎奄薇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抗愁,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馁蒂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜘腌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沫屡。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖撮珠,靈堂內(nèi)的尸體忽然破棺而出沮脖,到底是詐尸還是另有隱情,我是刑警寧澤芯急,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布勺届,位于F島的核電站,受9級特大地震影響娶耍,放射性物質(zhì)發(fā)生泄漏免姿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一榕酒、第九天 我趴在偏房一處隱蔽的房頂上張望胚膊。 院中可真熱鬧故俐,春花似錦、人聲如沸紊婉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肩榕。三九已至刚陡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間株汉,已是汗流浹背筐乳。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乔妈,地道東北人蝙云。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像路召,于是被迫代替她去往敵國和親勃刨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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