Retrofit源碼學(xué)習(xí)

本文針對(duì)的是Retrofit 2.1.0

一、Retrofit簡(jiǎn)介

如今開發(fā)一個(gè)Android應(yīng)用,幾乎都需要和網(wǎng)絡(luò)打交道,最常見的就是通過http協(xié)議來請(qǐng)求服務(wù)器數(shù)據(jù)或者是給服務(wù)器發(fā)送數(shù)據(jù),雖然Android本身也提供了HttpUrlConnection和Volley來發(fā)起http請(qǐng)求,功能上其實(shí)也能滿足,但鑒于Retrofit+OkHttp的組合如此流行,所以幾個(gè)項(xiàng)目下來,都是用的這一套。
??Retrofit是一個(gè)針對(duì)Android和java的類型安全的、開源的http客戶端,由大名鼎鼎的Square公司出品(另外還有:picasso、dagger等),通過注解的方式,讓開發(fā)者定義一個(gè)接口方法來完成http請(qǐng)求(真正的請(qǐng)求其實(shí)是由OkHttp來完成的),至于這個(gè)過程是怎么實(shí)現(xiàn)的,后面會(huì)詳細(xì)說。
相關(guān)鏈接:

二、使用

簡(jiǎn)單介紹下Retrofit的使用

  1. 定義一個(gè)接口:ApiService
public interface ApiService {

   /**
     * 獲取協(xié)議內(nèi)容
     *
     * @return 此時(shí)返回的JsonResponse里面的body字段的類型為 AgreementResponseBody
     */
    @GET("system/getAgreement")
    Call<JsonResponse> getAgreement();

    /**
     * 獲取點(diǎn)贊信息
     * @param getApprovalInfoBean
     * @return
     */
    @POST("approval/getApproval")
    Call<JsonResponse> getApprovalInfo(@Body GetApprovalInfoBean getApprovalInfoBean);
}
   

上面就定義了兩個(gè)最常用的http的方法,GET請(qǐng)求和POST請(qǐng)求,可以看到,需要使用GET或者是POST只需要在方法上加上注解就可以(當(dāng)然還有其它方法,像:DELETE等)。POST方法的話因?yàn)橛姓?qǐng)求體,所以會(huì)用@Body來注解,這里放的是我自定義的一個(gè)類(因?yàn)榻Y(jié)合了fastjson使用)。兩個(gè)方法的返回都是Call<T>的形式。

  1. Retrofit的相關(guān)配置及初始化
    ??Retrofit使用了Builder模式,主要是做了一些配置,可以看到,實(shí)例化了一個(gè)OkHttpClient,因?yàn)橐盟鼇磉M(jìn)行實(shí)際的http請(qǐng)求。
        OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
        builder.readTimeout(10, TimeUnit.SECONDS);
        builder.connectTimeout(9, TimeUnit.SECONDS);


        //添加攔截器,保留一些調(diào)試時(shí)的日志
        if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(interceptor);
        }

        OkHttpClient okHttpClient = builder.build();

        /**
         * addConverterFactory,是為了對(duì)象的序列化和反序列化,一般就是使用json相關(guān)的工具。
         * addCallAdapterFactory,是為了返回Call類型之外的其它類型
         */
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(okHttpClient)
                .addConverterFactory(FastJsonConverterFactory.create())
                .build();
        api = retrofit.create(ApiService.class);
  1. 發(fā)起請(qǐng)求
    ??一般用的比較多的是異步請(qǐng)求,當(dāng)然也可以使用同步請(qǐng)求。在Callback回調(diào)里面可以進(jìn)行成功及失敗,回調(diào)都是在主線程里面(在Android上使用,底層是通過Handler post到主線程)。

        Call<JsonResponse> call = apiService.getApprovalInfo(getApprovalInfoBean);
        requestList.add(call);

        call.enqueue(new Callback<JsonResponse>() {
            @Override
            public void onResponse(Call<JsonResponse> call, Response<JsonResponse> response) {
              
            }

            @Override
            public void onFailure(Call<JsonResponse> call, Throwable t) {

            }
        });

至此基本的用法就大概說完了,當(dāng)然還有很多其它的用法,具體可以參考下官網(wǎng)?;旧螲ttp的方法都支持。

三、源碼分析

  1. 源碼的包結(jié)構(gòu)


    image.png

    http包:


    image.png

http包里面放的都是一些自定義的注解類,平常開發(fā)者會(huì)用到的是:Call、CallAdapter、Callback、Converter、Retrofit


  1. 主要的幾個(gè)類之間的關(guān)系
類圖.jpg

  1. Retrofit是如何把一個(gè)接口方法轉(zhuǎn)變?yōu)閔ttp請(qǐng)求的嗎

Retrofit里面用到了大量的反射、泛型、注解,還用到了動(dòng)態(tài)代理(這是將接口方法轉(zhuǎn)換為http請(qǐng)求的關(guān)鍵,畢竟我們沒有看到 Call<JsonResponse> getAgreement();方法的實(shí)現(xiàn))。

可能自己之前寫的代碼比較簡(jiǎn)單,考慮的方面也比較單一,反射、注解之類的東西用的也比較少。拋開性能不說(反射會(huì)降低性能),這些功能(或者說是語(yǔ)法吧)能夠?qū)崿F(xiàn)一些“比較特殊”的功能,還有注解、泛型這些可以給讓代碼變得更加優(yōu)雅,增加代碼的可擴(kuò)展性和動(dòng)態(tài)性。感謝開源,讓我們能夠讀到大神的源碼。
??關(guān)鍵點(diǎn)是這里,使用了java的動(dòng)態(tài)代理,其實(shí)上面我們定義的接口方法getAgreement()真正的執(zhí)行地方是下面invoke()方法里面,通過反射調(diào)用了。

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

真正的方法調(diào)用:

return method.invoke(this, args);
return platform.invokeDefaultMethod(method, service, proxy, args);
return serviceMethod.callAdapter.adapt(okHttpCall);

這里可以看到有三個(gè)return的地方,第一種情況是:
這里又有一個(gè)關(guān)鍵的類:ServiceMethod,關(guān)于ServiceMethod,請(qǐng)看下文的“4”。


  1. ServiceMethod分析(關(guān)鍵銜接點(diǎn))

ServiceMethod就是具體來做相關(guān)的方法參數(shù)的解析(解析方法上面的注解)及解析方法的返回值。解析注解里面的值(http的參數(shù))其實(shí)是通過正則表達(dá)式匹配來完成的。

  static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
  static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");
  static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);

ServiceMethod仍然采用了Builder模式(Retrofit源碼里面可以看到大量使用Builder,對(duì)于有許多需要配置的參數(shù)的情況,這樣寫起來更優(yōu)雅一點(diǎn))。
最終的請(qǐng)求是由toRequest方法來完成:

/** Builds an HTTP request from method arguments. */
  Request toRequest(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 requestBuilder.build();
  }

返回的requestBuilder.build()產(chǎn)生了Request的實(shí)例,而Request是 OkHttp里面的類。由此可見,Retrofit只是封裝了http的請(qǐng)求,最終的請(qǐng)求還是通過OkHttp來完成的。


  1. Call接口

可用于異步也可用于同步請(qǐng)求,異步的話就放到隊(duì)列里,調(diào)用enqueue()方法;同步的話調(diào)用execute()方法,直接返回一個(gè)Response<T>對(duì)象。


  1. Callback接口

Call的實(shí)例調(diào)用enqueue()方法的參數(shù),用于接收回調(diào),包含兩個(gè)回調(diào)方法:

  • http層面上的正常響應(yīng),即網(wǎng)絡(luò)正常
/**
   * Invoked for a received HTTP response.
   * <p>
   * Note: An HTTP response may still indicate an application-level failure such as a 404 or 500.
   * Call {@link Response#isSuccessful()} to determine if the response indicates success.
   */
  void onResponse(Call<T> call, Response<T> response);
  • http層面上的異常響應(yīng)
/**
   * Invoked when a network exception occurred talking to the server or when an unexpected
   * exception occurred creating the request or processing the response.
   */
  void onFailure(Call<T> call, Throwable t);

一般在正常響應(yīng)里面需要判斷下應(yīng)用層面的情況,如:200、404等;判斷完應(yīng)用層面后需要再判斷下自定義的情況,一般:刷新成功、刷新失敗等。所以可以寫一個(gè)抽象類實(shí)現(xiàn)Callback接口,在里面做一些自已的處理,避免寫太多重復(fù)代碼。


  1. Converter<F, T>接口

將F類型轉(zhuǎn)換為T類型,主要用在轉(zhuǎn)成json對(duì)象,如:addConverterFactory(FastJsonConverterFactory.create())。
包含了一個(gè)方法和一個(gè)抽象工廠類:

T convert(F value) throws IOException;

abstract class Factory

抽象工廠類主要有三種轉(zhuǎn)換器方法:

public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
        Annotation[] annotations, Retrofit retrofit) {
      return null;
    }

   
    public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

   
    public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

分別是將ResponseBody轉(zhuǎn)成自己想要的類型,將自定義類型轉(zhuǎn)為RequestBody,將http的參數(shù)轉(zhuǎn)為字符串類型

跟這個(gè)相關(guān)的有一個(gè)自帶的類:
final class BuiltInConverters extends Converter.Factory

  • 重寫了responseBodyConverter和requestBodyConverter這兩個(gè)方法

  1. CallAdapter<R, T> 接口

這個(gè)接口常見的好像主要是為了配合Rxjava使用,因?yàn)檫€沒有用過Rxjava,所以等后面用了再作更深入的分析。
??主要用于將響應(yīng)的類型R轉(zhuǎn)換為自己想要的類型,用的比較多的是將響應(yīng)轉(zhuǎn)換為rxjava支持,會(huì)用到RxJava2CallAdapterFactory.create()。

在Retrofit.Builder#addCallAdapterFactory(Factory)里調(diào)用 ,一般在初始化里面。

包含了兩個(gè)方法和一個(gè)內(nèi)部的抽象類

Type responseType();

T adapt(Call<R> call);

abstract class Factory

抽象工廠方法里面包含了一個(gè)get()方法,返回一個(gè)CallAdapter類型;包含兩個(gè)返回類型的方法:

/**
     * Extract the upper bound of the generic parameter at {@code index} from {@code type}. For
     * example, index 1 of {@code Map<String, ? extends Runnable>} returns {@code Runnable}.
     */
    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    /**
     * Extract the raw class type from {@code type}. For example, the type representing
     * {@code List<? extends Runnable>} returns {@code List.class}.
     */
    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  1. 關(guān)于http包里的自定義注解類

這里定義注解是為了后面可以解析注解得到http請(qǐng)求對(duì)應(yīng)的方法及各種參數(shù)

  • 方法注解:GET、POST、PUT、DELETE、PATCH、HTTP、HEAD、FormUrlEncoded、Headers、Multipart、OPTIONS、Streaming
  • 參數(shù)注解:Body、Field、FieldMap、HeaderMap、Part、PartMap、Path、Query、QueryMap、Url

  1. Platform類(判斷是屬于哪個(gè)平臺(tái))

Retrofit的介紹說的就是可以在普通的java中使用,也可以在Android中使用,所以里面有一段代碼用來判斷當(dāng)前代碼是運(yùn)行在哪個(gè)平臺(tái)上。

private static Platform findPlatform() {
    try {
      Class.forName("android.os.Build");
      if (Build.VERSION.SDK_INT != 0) {
        return new Android();
      }
    } catch (ClassNotFoundException ignored) {
    }
    try {
      Class.forName("java.util.Optional");
      return new Java8();
    } catch (ClassNotFoundException ignored) {
    }
    try {
      Class.forName("org.robovm.apple.foundation.NSObject");
      return new IOS();
    } catch (ClassNotFoundException ignored) {
    }
    return new Platform();
  }

其實(shí)就是通過查找有沒有平臺(tái)相關(guān)的類來判斷具體是哪個(gè)平臺(tái),像在Android上就找“android.os.Build”;普通java的話,就通過找“java.util.Optional”來判斷;但是iOS查找“"org.robovm.apple.foundation.NSObject"”我就不是很明白了,iOS上也可以運(yùn)行嗎?
??這個(gè)小技巧感覺可以學(xué)習(xí)一下。

四、總結(jié)

之前使用Retrofit的時(shí)候也只是參照官網(wǎng)的例子簡(jiǎn)單使用了一下,并沒有去深究它內(nèi)部的實(shí)現(xiàn),最近在看自己寫的代碼的時(shí)候,發(fā)現(xiàn)每一次http請(qǐng)求都需要在回調(diào)里面做好多判斷,如:在onResponse()里面要判斷http的狀態(tài)碼,進(jìn)一步又要判斷自定義的狀態(tài),感覺好臃腫,于是就想到用抽象類來解決。最近剛好有點(diǎn)時(shí)間,就閱讀了一下源碼,發(fā)現(xiàn)收獲還是挺多的。

  • 通過注解的方式來完成http請(qǐng)求,對(duì)于我來說還算是比較新的。
  • 給方法、參數(shù)加上自定義注解,來增加一些編譯期和運(yùn)行時(shí)的行為。
  • 目前做項(xiàng)目的時(shí)候拋出異常的情況還比較少,而如果是設(shè)計(jì)一個(gè)庫(kù)供他人使用,可能就得像Retrofit一樣拋出一些異常,做一些參數(shù)檢查,來增加代碼的健壯性。
  • 使用工廠模式隱藏創(chuàng)建過程的復(fù)雜度,
  • 使用Builder模式來避免構(gòu)造方法里面有一堆的參數(shù)。
  • 使用泛型來增加代碼的可擴(kuò)展性,避免寫一堆的樣板代碼。

第一次寫博客,以前都是在有道云筆記里面記東西,雖然也記了挺多,但是都比較隨意。這一次會(huì)想寫有兩個(gè)原因:第一、應(yīng)工作室同學(xué)的邀請(qǐng);第二、學(xué)Android也有一段時(shí)間了,也用了不少開源庫(kù),看過一部分源碼,感覺需要好好總結(jié)一下,提升一下自己的技術(shù)水平。以前總是想著說自己的水平還不夠高,還沒到寫博客的時(shí)候,但是現(xiàn)在想想,其實(shí)也沒那么絕對(duì),寫出來是對(duì)自己的一個(gè)階段性總結(jié),對(duì)學(xué)過的東西的一個(gè)鞏固,一開始可能寫的不好,但寫多了,慢慢總會(huì)有進(jìn)步。之前看到過一句話“沒看過優(yōu)秀的源碼,是不太容易寫好優(yōu)秀的代碼的?!蔽覍?duì)此還是比較認(rèn)同的,開源的世界帶來了如此豐富的資源,需要我們好好利用。
??寫了一些代碼,也看過一些代碼,感覺看源碼就像在看書一樣,也有結(jié)構(gòu)、語(yǔ)法、先后順序,都是要表達(dá)些什么。只不過文字和源代碼是從不同方面來理解世界,從不同角度來構(gòu)筑這個(gè)世界。我越發(fā)地相信,這個(gè)世界有很多東西是相通的,是有聯(lián)系的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評(píng)論 25 708
  • Retrofit已經(jīng)出來很多時(shí)間了,項(xiàng)目中也一直在用,只知道它的功能強(qiáng)大,不知其原理,這不是一個(gè)號(hào)的開發(fā),所以最近...
    三季人閱讀 378評(píng)論 1 1
  • 基本用法 上面代碼主要?jiǎng)?chuàng)建了Retrofit對(duì)象,并且為Retrofit對(duì)象分別設(shè)置了OkhttpClient對(duì)象...
    寫代碼的閱讀 647評(píng)論 0 0
  • Retrofit是squareup公司的開源力作,和同屬squareup公司開源的OkHttp,一個(gè)負(fù)責(zé)網(wǎng)絡(luò)調(diào)度,...
    藍(lán)灰_q閱讀 42,132評(píng)論 23 281
  • 今天老師給我們講ZCS還有整體認(rèn)讀zⅰcⅰsi,課后我還自己畫了一幅小房子,科學(xué)老師還給我們放了小動(dòng)畫片兒。數(shù)學(xué)老...
    張雪涵閱讀 147評(píng)論 0 0

友情鏈接更多精彩內(nèi)容