Retrofit2 源碼解析

本文的源碼分析基于Retrofit 2,和Retrofit 1.0的Api有較大的不同, 本文主要分為幾部分:1、Retrofit 是什么,2、Retrofit怎么用,3、Retrofit的原理是什么,4、Retrofit的源碼分析。

1 Retrofit是什么

來自Retrofit官網的介紹:

A type-safe HTTP client for Android and Java

簡單的說它是一個基于OkHttp的RESTFUL Api請求工具,從功能上來說和Google的Volley功能上很相似,但是使用上很不相似。Volley使用上更加原始而且符合使用者的直覺,當App要發送一個Http請求時,你需要先創建一個Request對象,指定這個Request用的是GET、POST或其他方法,一個api 地址,一個處理response的回調,如果是一個POST請求,那么你還需要給這個Request對象設置一個body,有時候你還需要自定義添加Header什么的,然后將這個Request對象添加到RequestQueue中,接下去檢查Cache以及發送Http請求的事情,Volley會幫你處理。如果一個App中api不同的api請求很多,這樣代碼就會很難看。而Retrofit可以讓你簡單到調用一個Java方法的方式去請求一個api,這樣App中的代碼就會很簡潔方便閱讀

2 Retrofit怎么用

雖然Retrofit官網已經說明了,我還是要按照我的思路說一下它的使用方法

首先,你需要創建一個Retrofit
對象,并且指定api的域名:

public static final String API_URL = "https://zhuanlan.zhihu.com";
//Create a very simple REST adapter which points the Zhuanlan API.
Retrofit retrofit = new Retrofit.Builder() 
                              .baseUrl(API_URL) 
                              .addConverterFactory(GsonConverterFactory.create()) 
                              .build();

其次,你要根據api新建一個Java接口,用Java注解來描述這個api

public interface ZhuanLanApi { 

    @GET("/api/columns/{user} ") Call<ZhuanLanAuthor>
    getAuthor(@Path("user") String user)

}

再用這個retrofit對象創建一個ZhuanLanApi對象:

    ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);

    Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");

這樣就表示你要請求的api是https://zhuanlan.zhihu.com/api/columns/qinchao

最后你就可以用這個call對象獲得數據了,enqueue方法是異步發送http請求的,如果你想用同步的方式發送可以使用execute()方法,call對象還提供cancel()、isCancel()等方法獲取這個Http請求的狀態

// 請求數據,并且處理
responsecall.enqueue(new Callback<ZhuanLanAuthor>() { 
    @Override 
    public void onResponse(Response<ZhuanLanAuthor> author) {

         System.out.println("name: " + author.getName()); 
    } 
    @Override 
    public void onFailure(Throwable t) { 

    }
  });

3 Retrofit的原理

從上面Retrofit的使用來看,Retrofit就是充當了一個適配器(Adapter)的角色:將一個Java接口翻譯成一個Http請求,然后用OkHttp去發送這個請求Volley描述一個HTTP請求是需要創建一個Request對象,而執行這個請求呢,就是把這個請求對象放到一個隊列中,在網絡線程中用HttpUrlConnection去請求
Retrofit是怎么做的呢?
就是:
Java的動態代理**

動態代理

ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);

我給Retrofit對象傳了一個ZhuanLanApi接口的Class對象,怎么又返回一個ZhuanLanApi對象呢?進入create方法一看,沒幾行代碼,但是我覺得這幾行代碼就是Retrofit的精妙的地方

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

create方法就是返回了一個Proxy.newProxyInstance動態代理對象。

為什么要使用動態代理

你看上面代碼,獲取數據的代碼就是這句:

Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");

上面api對象其實是一個動態代理對象,并不是一個真正的ZhuanLanApi接口的implements產生的對象,當api對象調用getAuthor方法時會被動態代理攔截,然后調用Proxy.newProxyInstance方法中的InvocationHandler對象,它的invoke方法會傳入3個參數:

  • Object proxy: 代理對象,不關心這個
  • Method method:調用的方法,就是getAuthor方法
  • Object... args:方法的參數,就是"qinchao"

而Retrofit關心的就是method和它的參數args,接下去Retrofit就會用Java反射獲取到getAuthor方法的注解信息,配合args參數,創建一個ServiceMethod對象

ServiceMethod就像是一個中央處理器,傳入Retrofit對象和Method對象,調用各個接口和解析器,最終生成一個Request,包含api 的域名、path、http請求方法、請求頭、是否有body、是否是multipart等等。最后返回一個Call對象,Retrofit2中Call接口的默認實現是OkHttpCall,它默認使用OkHttp3作為底層http請求client

使用Java動態代理的目的就要攔截被調用的Java方法,然后解析這個Java方法的注解,最后生成Request由OkHttp發送

4 Retrofit的源碼分析

先來看一下Retrofit源碼的組成:

  1. 一個retrofit2.http包,里面全部是定義HTTP請求的Java注解,比如GET、POST、PUT、DELETE、Headers、Path、Query等等。

  2. 余下的retrofit2包中幾個類和接口就是全部retrofit的代碼了,代碼真的很少,很簡單,因為retrofit把網絡請求這部分功能全部交給了OkHttp了

Retrofit接口

Retrofit的設計非常插件化而且輕量級,真的是非常高內聚而且低耦合,這個和它的接口設計有關。Retrofit中定義了4個接口:

Callback<T>
這個接口就是retrofit請求數據返回的接口,只有兩個方法

  • void onResponse(Response<T> response);
  • void onFailure(Throwable t);

Converter<F, T>
這個接口主要的作用就是將HTTP返回的數據解析成Java對象,主要有Xml、Gson、protobuf等等,你可以在創建Retrofit對象時添加你需要使用的Converter實現(看上面創建Retrofit對象的代碼)

Call<T>
這個接口主要的作用就是發送一個HTTP請求,Retrofit默認的實現是OkHttpCall<T>,你可以根據實際情況實現你自己的Call類,這個設計和Volley的HttpStack接口設計的思想非常相似,子類可以實現基于HttpClient或HttpUrlConnetction的HTTP請求工具,這種設計非常的插件化,而且靈活

CallAdapter<T>
上面說到過,CallAdapter中屬性只有responseType一個,還有一個<R> T adapt(Call<R> call)方法,這個接口的實現類也只有一個,DefaultCallAdapter。這個方法的主要作用就是將Call對象轉換成另一個對象,可能是為了支持RxJava才設計這個類的吧

Retrofit的運行過程

面講到ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);代碼返回了一個動態代理對象,而執行Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");代碼時返回了一個OkHttpCall對象,拿到這個Call對象才能執行HTTP請求。

上面api對象其實是一個動態代理對象,并不是一個真正的ZhuanLanApi接口的implements產生的對象,當api對象調用getAuthor方法時會被動態代理攔截,然后調用Proxy.newProxyInstance方法中的InvocationHandler對象, 創建一個ServiceMethod對象

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

創建ServiceMethod

剛才說到,ServiceMethod就像是一個中央處理器,具體來看一下創建這個ServiceMethod的過程是怎么樣的

第一步,獲取到上面說到的3個接口對象:

callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
responseConverter = createResponseConverter();

第二步,解析Method的注解,主要就是獲取Http請求的方法,比如是GET還是POST還是其他形式,如果沒有,程序就會報錯,還會做一系列的檢查,比如如果在方法上注解了@Multipart,但是Http請求方法是GET,同樣也會報錯。因此,在注解Java方法是需要嚴謹

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

第三步,比如上面api中帶有一個參數{user},這是一個占位符,而真實的參數值在Java方法中傳入,那么Retrofit會使用一個ParameterHandler來進行替換:

int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];

最后,ServiceMethod會做其他的檢查,比如用了@FormUrlEncoded
注解,那么方法參數中必須至少有一個@Field或@FieldMap

執行Http請求

之前講到,OkHttpCall是實現了Call接口的,并且是真正調用OkHttp3發送Http請求的類。OkHttp3發送一個Http請求需要一個Request對象,而這個Request對象就是從ServiceMethod的toRequest返回的

總的來說,OkHttpCall就是調用ServiceMethod獲得一個可以執行的Request對象,然后等到Http請求返回后,再將response body傳入ServiceMethod中,ServiceMethod就可以調用Converter接口將response body轉成一個Java對象

結合上面說的就可以看出,ServiceMethod中幾乎保存了一個api請求所有需要的數據,OkHttpCall需要從ServiceMethod中獲得一個Request對象,然后得到response后,還需要傳入ServiceMethod用Converter轉換成Java對象

如何在Retrofit中使用RxJava

由于Retrofit設計的擴展性非常強,你只需要添加一個CallAdapter就可以了

Retrofit retrofit = new Retrofit.Builder()
  .baseUrl("https://api.github.com")
  .addConverterFactory(ProtoConverterFactory.create())
  .addConverterFactory(GsonConverterFactory.create())
  .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
  .build();

上面代碼創建了一個Retrofit對象,支持Proto和Gson兩種數據格式,并且還支持RxJava。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容