Retrofit2 原理解析

Retrofit是什么

官網(wǎng)介紹是A type-safe HTTP client for Android and Java排霉,是一個(gè) RESTful 的 HTTP 網(wǎng)絡(luò)請(qǐng)求框架的封裝,但網(wǎng)絡(luò)請(qǐng)求不是Retrofit來(lái)完成的,它只是封裝了請(qǐng)求參數(shù)往果、Header、Url、返回結(jié)果處理等信息,而請(qǐng)求是由OkHttp3來(lái)完成的色乾。

入門(mén)

Retrofit入門(mén)非常簡(jiǎn)單,首先需要在build.gradle引用相關(guān)依賴(lài)

implementation 'com.squareup.retrofit2:retrofit:2.5.0'

定義一個(gè)HTTP API接口類(lèi)

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

使用Retrofit類(lèi)生成GitHubService 接口實(shí)現(xiàn)

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

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

發(fā)送HTTP請(qǐng)求领突,返回Response可以同步或者異步處理

Call<List<Repo>> repos = service.listRepos("octocat");
// 同步
List<Repo> data = repos.execute(); 
// 異步
repos.enqueue(new Callback<List<Repo>>() {
            @Override
            public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
                List<Repo> data = response.body();
            }

            @Override
            public void onFailure(Call<List<Repo>> call, Throwable t) {
                t.printStackTrace();
            }
        });

Retrofit入門(mén)就是這幾步暖璧,當(dāng)然在實(shí)際使用的時(shí)候肯定沒(méi)有那么簡(jiǎn)單,可以根據(jù)具體需求來(lái)處理君旦,之前寫(xiě)過(guò)一篇文章Retrofit+Rxjava的封裝澎办,具體可以去看看嘲碱。

解讀

Retrofit庫(kù)有四個(gè)module,包含retrofit浮驳,adapter悍汛,convert,mock等至会,我們先來(lái)看看Retrofit整體結(jié)構(gòu),先對(duì)它有個(gè)大體的了解谱俭。

  • 請(qǐng)求方法

Retrofit定義了請(qǐng)求注解類(lèi)奉件,支持請(qǐng)求方法包含GET、POST昆著、HEAD县貌、OPTIONS、PUT凑懂、DELETE煤痕、PATCH請(qǐng)求,當(dāng)然你也可以直接使用HTTP自定義請(qǐng)求接谨。例如以GET請(qǐng)求為例摆碉,

@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
  /**
   * A relative or absolute path, or full URL of the endpoint. This value is optional if the first
   * parameter of the method is annotated with {@link Url @Url}.
   * <p>
   * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how
   * this is resolved against a base URL to create the full endpoint URL.
   */
  String value() default "";
}

GET定義了一個(gè)value,這個(gè)值是相關(guān)請(qǐng)求的path脓豪,而我們?cè)趧?chuàng)建Retrofit的時(shí)候已經(jīng)傳入一個(gè)baseUrl巷帝,這兩個(gè)會(huì)組裝成真正的請(qǐng)求url。如果想使用HTTP自定義扫夜,可以這樣定義:

HTTP(method = "DELETE", path = "remove/", hasBody = true)
  • 請(qǐng)求參數(shù)

Retrofit定義了請(qǐng)求參數(shù)注解類(lèi)楞泼,包含Body、Field笤闯、FieldMap堕阔、Header、HeaderMap颗味、Part超陆、PartMap、Query脱衙、QueryMap侥猬、QueryName。以Query為例捐韩,例如 http://api.github.com/list?page=10退唠,可以寫(xiě)成下面的代碼。

@GET("/list")
Call<ResponseBody> list(@Query("page") int page);

使用POST的時(shí)候荤胁,絕大多數(shù)的服務(wù)端接口都需要做加密瞧预、鑒權(quán)和校驗(yàn),可以使用@Field來(lái)處理參數(shù)

@POST("/list")
Call<ResponseBody> list(@Field("page") int page);

而Map結(jié)尾的注解參數(shù)類(lèi),其實(shí)就是數(shù)據(jù)集垢油,如@QueryMap Map<String, String> map

  • Converter

在Retrofit中盆驹,無(wú)論是發(fā)送數(shù)據(jù)和接收數(shù)據(jù),都是通過(guò)OKHttp的RequestBody和ResponseBody來(lái)實(shí)現(xiàn)的滩愁。在實(shí)際項(xiàng)目中躯喇,有時(shí)候原始的RequestBody或是ResponseBody并不能滿足我們的需求(如接口加密),就需要對(duì)它進(jìn)行轉(zhuǎn)換硝枉。而且Retrofit官方給了以下幾個(gè)常用的轉(zhuǎn)換庫(kù)廉丽。

  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

這對(duì)于一般的使用來(lái)說(shuō)確實(shí)夠用了,但是如果我們對(duì)安全性要求比較高妻味,或者編碼不太一樣的話正压,這些庫(kù)就沒(méi)法使用了,于是我們就需要自定義ConverterFactory责球。Retrofit已經(jīng)為我們提供了自定義Converter.Factory的接口焦履,我們只需要實(shí)現(xiàn)它給的接口即可。

public final class ProtoConverterFactoryCompat extends Converter.Factory {
    public static ProtoConverterFactoryCompat create() {
        return new ProtoConverterFactoryCompat(null);
    }

    /**
     * Create an instance which uses {@code registry} when deserializing.
     */
    public static ProtoConverterFactoryCompat createWithRegistry(ExtensionRegistryLite registry) {
        return new ProtoConverterFactoryCompat(registry);
    }

    private final ExtensionRegistryLite registry;

    private ProtoConverterFactoryCompat(ExtensionRegistryLite registry) {
        this.registry = registry;
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                            Retrofit retrofit) {
        if (!(type instanceof Class<?>)) {
            return null;
        }
        Class<?> c = (Class<?>) type;
        if (!MessageLite.class.isAssignableFrom(c)) {
            return null;
        }

        Parser<MessageLite> parser = null;
        try {
            parser = ProtoJavas.getParser(c);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        if (parser == null)
            throw new IllegalArgumentException(
                    "Found a protobuf message but " + c.getName() + " had no PARSER field.");

        return new ProtoResponseBodyConverterCompat<>(parser, registry);
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                          Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        if (!(type instanceof Class<?>)) {
            return null;
        }
        if (!MessageLite.class.isAssignableFrom((Class<?>) type)) {
            return null;
        }
        return new ProtoRequestBodyConverterCompat<>();
    }

原理

  • 初始化

Retrofit采用了Builder模式雏逾,進(jìn)行了一系列的初始化操作嘉裤,在build里面把我們初始化傳入的參數(shù)進(jìn)行整合,返回給我們一個(gè)Retrofit對(duì)象校套。

public Retrofit build() {
  if (baseUrl == null) {
    throw new IllegalStateException("Base URL required.");
  }

  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {
    callFactory = new OkHttpClient();
  }

  Executor callbackExecutor = this.callbackExecutor;
  if (callbackExecutor == null) {
    callbackExecutor = platform.defaultCallbackExecutor();
  }

  // Make a defensive copy of the adapters and add the default Call adapter.
  List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
  callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

  // Make a defensive copy of the converters.
  List<Converter.Factory> converterFactories = new ArrayList<>(
      1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());

  // Add the built-in converter factory first. This prevents overriding its behavior but also
  // ensures correct behavior when using converters that consume all types.
  converterFactories.add(new BuiltInConverters());
  converterFactories.addAll(this.converterFactories);
  converterFactories.addAll(platform.defaultConverterFactories());

  return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
      unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}

這里面我們主要看下面這個(gè)幾個(gè)參數(shù)价脾。

  • callFactory對(duì)okhttp的自定義,比如攔截器公共參數(shù)的設(shè)置等
  • callbackExecutor 可不設(shè)置笛匙,會(huì)根據(jù)平臺(tái)來(lái)創(chuàng)建默認(rèn)的
  • converterFactories存儲(chǔ)對(duì)轉(zhuǎn)換的支持侨把,在我們請(qǐng)求服務(wù)器數(shù)據(jù)是返回的時(shí)候自動(dòng)解析成我們需要的bean類(lèi)型
  • callAdapterFactories 添加適配器支持,可設(shè)置Rxjava的支持
  • 接口類(lèi)

前面已經(jīng)說(shuō)了如何使用retrofit妹孙,首先創(chuàng)建了一個(gè)server接口秋柄,使用的時(shí)候肯定不是接口實(shí)現(xiàn)的,但它是如何使用的呢蠢正?其實(shí)retrofit使用了動(dòng)態(tài)代理來(lá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();
        private final Object[] emptyArgs = new Object[0];

        @Override public Object invoke(Object proxy, Method method, @Nullable 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);
          }
          return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
        }
      });
}

ServiceMethod<?> loadServiceMethod(Method method) {
  ServiceMethod<?> result = serviceMethodCache.get(method);
  if (result != null) return result;

  synchronized (serviceMethodCache) {
    result = serviceMethodCache.get(method);
    if (result == null) {
      result = ServiceMethod.parseAnnotations(this, method);
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}

ServiceMethod中保存方法緩存,如果沒(méi)有就新創(chuàng)建然后添加到緩存里嚣崭,并且這里返回的是一個(gè)動(dòng)態(tài)代理InvocationHandler笨触。

  • 請(qǐng)求處理

Retrofit 通過(guò)invoke為我們構(gòu)造了一個(gè) OkHttpCall ,實(shí)際上每一個(gè) OkHttpCall 都對(duì)應(yīng)于一個(gè)請(qǐng)求雹舀,它主要完成最基礎(chǔ)的網(wǎng)絡(luò)請(qǐng)求芦劣,而我們?cè)诮涌诘姆祷刂锌吹降?Call 默認(rèn)情況下就是 OkHttpCall 了,如果我們添加了自定義的 callAdapter说榆,那么它就會(huì)將 OkHttp 適配成我們需要的返回值虚吟,并返回給我們寸认。

@Override ReturnT invoke(Object[] args) {
  return callAdapter.adapt(
      new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
}

最后通過(guò)OkHttpCall.execute發(fā)起網(wǎng)絡(luò)請(qǐng)求

@Override public void enqueue(final Callback<T> callback) {
  checkNotNull(callback, "callback == null");

  okhttp3.Call call;
  Throwable failure;

  synchronized (this) {
    if (executed) throw new IllegalStateException("Already executed.");
    executed = true;

    call = rawCall;
    failure = creationFailure;
    if (call == null && failure == null) {
      try {
        call = rawCall = createRawCall();
      } catch (Throwable t) {
        throwIfFatal(t);
        failure = creationFailure = t;
      }
    }
  }

  if (failure != null) {
    callback.onFailure(this, failure);
    return;
  }

  if (canceled) {
    call.cancel();
  }

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

      try {
        callback.onResponse(OkHttpCall.this, response);
      } catch (Throwable t) {
        t.printStackTrace();
      }
    }

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

    private void callFailure(Throwable e) {
      try {
        callback.onFailure(OkHttpCall.this, e);
      } catch (Throwable t) {
        t.printStackTrace();
      }
    }
  });
}

看到 OkHttpCall 其實(shí)也是封裝了 okhttp3.Call,在這個(gè)方法中串慰,我們通過(guò) okhttp3.Call 發(fā)起了請(qǐng)求偏塞。而parseResponse 主要完成了由 okhttp3.Response 向 retrofit.Response 的轉(zhuǎn)換,同時(shí)也處理了對(duì)原始返回的解析邦鲫。

總結(jié)

  • Retrofit通過(guò)動(dòng)態(tài)代理灸叼,用MethodHandler完成接口方法。
  • Retrofit的MethodHandler通過(guò)RequestFactoryParser.parse解析掂碱,獲得接口方法的參數(shù)和注解的值怜姿,傳入到OkHttpCall,OkHttpCall生成okhttp3.Call完成Http請(qǐng)求并使用Converter解析數(shù)據(jù)回調(diào)疼燥。
  • Retrofit通過(guò)工廠設(shè)置CallAdapter和Converter,CallAdapter包裝轉(zhuǎn)換Call蚁堤,Converter轉(zhuǎn)換(解析)服務(wù)器返回的數(shù)據(jù)醉者、接口方法的注解參數(shù)。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末披诗,一起剝皮案震驚了整個(gè)濱河市撬即,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌呈队,老刑警劉巖剥槐,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異宪摧,居然都是意外死亡粒竖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)几于,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蕊苗,“玉大人,你說(shuō)我怎么就攤上這事沿彭⌒嗯椋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵喉刘,是天一觀的道長(zhǎng)瞧柔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)睦裳,這世上最難降的妖魔是什么造锅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮推沸,結(jié)果婚禮上备绽,老公的妹妹穿的比我還像新娘券坞。我一直安慰自己,他們只是感情好肺素,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布恨锚。 她就那樣靜靜地躺著,像睡著了一般倍靡。 火紅的嫁衣襯著肌膚如雪猴伶。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天塌西,我揣著相機(jī)與錄音他挎,去河邊找鬼。 笑死捡需,一個(gè)胖子當(dāng)著我的面吹牛办桨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播站辉,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼呢撞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了饰剥?” 一聲冷哼從身側(cè)響起殊霞,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎汰蓉,沒(méi)想到半個(gè)月后绷蹲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顾孽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年祝钢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岩齿。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡太颤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盹沈,到底是詐尸還是另有隱情龄章,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布乞封,位于F島的核電站做裙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏肃晚。R本人自食惡果不足惜锚贱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望关串。 院中可真熱鬧拧廊,春花似錦监徘、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至倦春,卻和暖如春户敬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睁本。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工尿庐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人呢堰。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓抄瑟,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親枉疼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锐借,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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