談?wù)剬?duì)Retrofit的理解

概述

App的開發(fā)過程中,少不了和網(wǎng)絡(luò)打交道。從最開始用的Android Async Http到Volley再到現(xiàn)在的Retrofit,Retrofit的使用方式給了我很深的印象店乐,于是就看了它的源碼實(shí)現(xiàn),加深對(duì)它的學(xué)習(xí)和理解呻袭。

Retrofit介紹

retrofit其實(shí)不是一個(gè)網(wǎng)絡(luò)請(qǐng)求庫(kù)眨八,算是一個(gè)請(qǐng)求庫(kù)上層的一個(gè)使用封裝,發(fā)請(qǐng)求的實(shí)現(xiàn)還是用的OKHttp左电。官方文檔中有這么一句話:Use annotations to describe the HTTP request廉侧。即使用注解的方式去描述Http請(qǐng)求。Retrofit的實(shí)際作用就是讓開發(fā)者可以通過注解很方便的產(chǎn)生請(qǐng)求并發(fā)起請(qǐng)求篓足,不需要自己去構(gòu)建Request段誊。

Retrofit使用及源碼分析

每個(gè)請(qǐng)求都要有API,Retrofit的API定義方式是通過接口的形式栈拖,如下代碼:

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

很多人可能會(huì)覺得困惑连舍,沒看到API地址吖,這里只有一個(gè)相對(duì)的"users/{user}/repos"涩哟,別急索赏,我們?cè)偻驴词褂谩?/p>

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);

首先是在創(chuàng)建Retrofit時(shí)指定了baseUrl盼玄,這個(gè)就是用此Retrofit創(chuàng)建的請(qǐng)求都是以這個(gè)baseUrl為基礎(chǔ)的。

然后通過retrofit.create(GitHubService.class);來創(chuàng)建一個(gè)GitHubService 的實(shí)例潜腻,這里使用的是動(dòng)態(tài)代理埃儿,來看看這里面的實(shí)現(xiàn)吧。

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

Utils.validateServiceInterface(service);是對(duì)傳入的service進(jìn)行校驗(yàn)砾赔,Retrofit要求接口定義必需是interface蝌箍,并且不能extend其它的interface青灼。

Proxy.newProxyInstance這個(gè)就是動(dòng)態(tài)代理的實(shí)現(xiàn)了暴心。Java動(dòng)態(tài)代理就是給了程序員一種可能:當(dāng)你要調(diào)用某個(gè)Class的方法前或后,插入你想要執(zhí)行的代碼杂拨。Retrofit實(shí)現(xiàn)動(dòng)態(tài)代理就是要在調(diào)用接口的方法時(shí)专普,通過反射去解析注解,解析參數(shù)弹沽,動(dòng)態(tài)去生成一個(gè)Request請(qǐng)求檀夹,這也就印證了官方的Use annotations to describe the HTTP request這句話了。

具體的轉(zhuǎn)換在 ServiceMethod serviceMethod = loadServiceMethod(method);里策橘,來看下loadServiceMethod的實(shí)現(xiàn):

ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result;
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

可以看到炸渡,這里做了一個(gè)緩存,避免多次生成ServiceMethod丽已,可以提高性能蚌堵。我們?cè)偃タ聪戮唧w的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);
    }

這里初始化了callAdapter、responseType沛婴、responseConverter這些變量吼畏,用于發(fā)起請(qǐng)求和解析response的。這里有一行很關(guān)鍵的代碼就是:

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

這里對(duì)方法的注解進(jìn)行了分析嘁灯,來看下里面的邏輯吧:

 private void parseMethodAnnotation(Annotation annotation) {
      if (annotation instanceof DELETE) {
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
      } else if (annotation instanceof GET) {
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
      } else if (annotation instanceof HEAD) {
        parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
        if (!Void.class.equals(responseType)) {
          throw methodError("HEAD method must use Void as response type.");
        }
      } else if (annotation instanceof PATCH) {
        parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
      } else if (annotation instanceof POST) {
        parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
      } else if (annotation instanceof PUT) {
        parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
      } else if (annotation instanceof OPTIONS) {
        parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
      } else if (annotation instanceof HTTP) {
        HTTP http = (HTTP) annotation;
        parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
      } else if (annotation instanceof retrofit2.http.Headers) {
        String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
        if (headersToParse.length == 0) {
          throw methodError("@Headers annotation is empty.");
        }
        headers = parseHeaders(headersToParse);
      } else if (annotation instanceof Multipart) {
        if (isFormEncoded) {
          throw methodError("Only one encoding annotation is allowed.");
        }
        isMultipart = true;
      } else if (annotation instanceof FormUrlEncoded) {
        if (isMultipart) {
          throw methodError("Only one encoding annotation is allowed.");
        }
        isFormEncoded = true;
      }
    }

哇泻蚊,這就是我們使用的請(qǐng)求類型注解了,像GET丑婿、POST性雄、PUT都有呢,原來就是在這里進(jìn)行解析的羹奉。

回過頭來繼續(xù)看create函數(shù):

ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);

Retrofit默認(rèn)使用OkHttp來發(fā)起請(qǐng)求秒旋,這里還做了一個(gè)適配,通過callAdapter把請(qǐng)求轉(zhuǎn)成想要的類型尘奏,比如RxJava2CallAdapter滩褥,就會(huì)把請(qǐng)求傳成Observable<?>類型。

源碼分析就先到這里了炫加,相信也有助于大家去理解Retrofit的使用了原理了瑰煎。

總結(jié)

Retrofit并不是一個(gè)網(wǎng)絡(luò)請(qǐng)求庫(kù)铺然,是一個(gè)網(wǎng)絡(luò)請(qǐng)求的上層封裝,提供友好的接口讓使用者自定義請(qǐng)求酒甸。源碼里使用了大量的工廠模式魄健,適配器模式,設(shè)計(jì)感很強(qiáng)插勤,方便擴(kuò)展和自定義沽瘦。官方提供了RxJava的支持,和RxJava結(jié)合起來用還是很爽的农尖。如果服務(wù)器的接口是用Restful的析恋,那是再合適不過了!盛卡!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末助隧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子滑沧,更是在濱河造成了極大的恐慌并村,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滓技,死亡現(xiàn)場(chǎng)離奇詭異哩牍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)令漂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門膝昆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洗显,你說我怎么就攤上這事外潜。” “怎么了挠唆?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵处窥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我玄组,道長(zhǎng)滔驾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任俄讹,我火速辦了婚禮哆致,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘患膛。我一直安慰自己摊阀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著胞此,像睡著了一般臣咖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漱牵,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天夺蛇,我揣著相機(jī)與錄音,去河邊找鬼酣胀。 笑死刁赦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的闻镶。 我是一名探鬼主播甚脉,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼儒溉!你這毒婦竟也來了宦焦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤顿涣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后酝豪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涛碑,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年孵淘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蒲障。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瘫证,死狀恐怖揉阎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情背捌,我是刑警寧澤毙籽,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站毡庆,受9級(jí)特大地震影響坑赡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜么抗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一毅否、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝇刀,春花似錦螟加、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甸昏。三九已至,卻和暖如春徐许,著一層夾襖步出監(jiān)牢的瞬間施蜜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工雌隅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翻默,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓恰起,卻偏偏與公主長(zhǎng)得像修械,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子检盼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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