Retrofit原理淺析
做Android也有幾年了,各種Android http類庫也用過不少,自己的做過的項目中也一直在嘗試怎么封裝讓API接口定義和API使用者解耦,但一直感覺沒有可以讓人滿意的框架,直到無意中在網(wǎng)上看到了JakeWharton大神的Retrofit這個類庫。不得不說,真心是炒雞解耦。
下面來簡單分析一下這個Retrofit2的使用和實現(xiàn)。
類庫使用示例
首先定義請求接口
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributors(
@Path("owner") String owner,
@Path("repo") String repo);
}
然后通過Retrofit生成一個剛才定義的接口的實現(xiàn)類,使用的是動態(tài)代理。
// Create a very simple REST adapter which points the GitHub API.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
// Create an instance of our GitHub API interface.
GitHub github = retrofit.create(GitHub.class);
之后就可以使用接口進行請求了
// Create a call instance for looking up Retrofit contributors.
Call<List<Contributor>> call = github.contributors("square", "retrofit");
類庫原理解析
注解
Retrofit使用注解+java接口來定義后臺服務API接口
注解主要分為 方法注解 和 參數(shù)注解
| 注解 | 類型 | 作用 |
|---|---|---|
| @GET | 方法注解 | 表明HTTP請求方法為GET,(可選)注解的value屬性用來設置相對/絕對url |
| @POST | 方法注解 | 表明HTTP請求方法為POST,(可選)注解的value屬性用來設置相對/絕對url |
| @PUT | 方法注解 | 表明HTTP請求方法為PUT,(可選)注解的value屬性用來設置相對/絕對url |
| @DELETE | 方法注解 | 表明http請求方法為DELETE,(可選)注解的value屬性用來設置相對/絕對url |
| @PATCH | 方法注解 | 表明HTTP請求方法為PATCH,(可選)注解的value屬性用來設置相對/絕對url |
| @HEAD | 方法注解 | 表明HTTP請求方法為HEAD,(可選)注解的value屬性用來設置相對/絕對url |
| @OPTIONS | 方法注解 | 表明HTTP請求方法為OPTIONS,(可選)注解的value屬性用來設置相對/絕對url |
| @HTTP | 方法注解 | 通過@HTTP注解指定http協(xié)議的請求方法,是否允許body,(可選)注解的value屬性用來設置相對/絕對url |
| @FormUrlEncoded | 方法注解 | 表明發(fā)起HTTP請求的RequestBody是form表單方式 |
| @Multipart | 方法注解 | 表明發(fā)起HTTP請求的RequestBody是Multipar方式 |
| @Headers | 方法注解 | 使用注解的value值數(shù)組作為HTTP請求的頭,用于一些固定的Header參數(shù) |
| @Streaming | 方法注解 | 用于需要直接返回流的函數(shù) |
| @Url | 參數(shù)注解 | HTTP請求的url路徑(相對/絕對),可以包含{path_holder},如:http://xxx.com/{user_holder}/detail |
| @Path | 參數(shù)注解 | 用于動態(tài)替換URL路徑中的path_holder |
| @Body | 參數(shù)注解 | 表明此參數(shù)用作HTTP請求的body |
| @Field | 參數(shù)注解 | 表明此參數(shù)用作HTTP請求的form表單參數(shù),key為注解的value值 |
| @FieldMap | 參數(shù)注解 | 以map形式傳入的form表單參數(shù) |
| @Header | 參數(shù)注解 | 表明此參數(shù)用作HTTP請求的header,key為注解的value值 |
| @HeaderMap | 參數(shù)注解 | 以map形式傳入的多個header鍵值對 |
| @Part | 參數(shù)注解 | 表明參數(shù)為Http的multipart參數(shù)之一 |
| @PartMap | 參數(shù)注解 | 以map形式傳入的multipart參數(shù)表 |
| @Query | 參數(shù)注解 | GET方法的query參數(shù),用于拼接完整請求路徑 |
| @QueryMap | 參數(shù)注解 | 以map傳入的GET方法的query參數(shù),用于拼接完整請求路徑 |
生成動態(tài)代理實例
Retrofit使用的關鍵一步就是Retrofit.create函數(shù)創(chuàng)建接口動態(tài)代理的示例,代碼如下
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
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);
}
});
}
可以看到是為接口的每個method創(chuàng)建了一個對應的ServiceMethod,并使用這個ServiceMethod對象創(chuàng)建OkHttpCall,并使用ServiceMethod實例的callAdapter來調用okhttpCall并返回結果。
調用流程
通過上面代碼可以看到調用關鍵的就是三步:
- 1 加載對應method的ServiceMethod實例
- 2 使用ServiceMethod實例和方法調用參數(shù)創(chuàng)建OkHttpCall
- 3 調用serviceMethod.callAdapter.adapt(okHttpCall)來產(chǎn)生method所定義的返回(Call<T>或者其他自定義CallAdapter支持的返回)
第一步、加載對應method的ServiceMethod實例
ServiceMethod中有以下四個變量比較重要
final okhttp3.Call.Factory callFactory;
final CallAdapter<?> callAdapter;
private final Converter<ResponseBody, T> responseConverter;
private final ParameterHandler<?>[] parameterHandlers;
- callFactory是用來創(chuàng)建真正要執(zhí)行的okhttp3.Call的工廠類,可以Retrofit.Builder中設置,如果不設置,默認會new一個OkHttpClient作為callFactory
- callAdapter是用來最終處理OkHttpCall實例并返回接口Method所定義的返回
- responseConverter 用來將Http請求的結果轉換成接口Method所定義的結果(return或者Callback<T>中的T)
- parameterHandlers 根據(jù)接口Method參數(shù)的注解所生成的參數(shù)處理Handler數(shù)組
然后我們來看Retrofit.loadServiceMethod方法
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;
}
可以看到此處先檢查serviceMethodCache是否有該method對應的ServiceMethod實例緩存,如果沒有,則創(chuàng)建一個該method對應的ServiceMethod實例并保存到緩存中。
ServiceMethod的創(chuàng)建使用的是建造者模式。
在ServiceMethod.Builder的build方法中,通過解析傳入的method的方法定義(參數(shù)類型,返回類型,參數(shù)注解,方法注解)生成對應的callAdapter,responseConverter,parameterHandlers及其他一些創(chuàng)建請求需要用到的信息。
public ServiceMethod build() {
callAdapter = createCallAdapter();
......檢查返回結果類型......
responseConverter = createResponseConverter();
//生成方法注解的處理器
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
.....方法與注解合法性檢查.....
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
.....注解合法性檢查....
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
......方法與注解合法性檢查......
return new ServiceMethod<>(this);
}
此處在ServiceMethod.Builder.build()過程在生成過程中還會對method的定義做合法性檢查,如:http方法是get就不允許方法參數(shù)中有body類型的參數(shù);方法為post則必須有參數(shù)為Body類型。
第二步、使用ServiceMethod實例和方法調用參數(shù)創(chuàng)建OkHttpCall
獲取到method對應的ServiceMethod實例后,會使用該ServiceMethod實例和方法調用的參數(shù)Object... args生成一個OkHttpCall。而OkHttpCall實際上是okhttp3.Call的一個包裝類,實際調用OkHttpCall的相關執(zhí)行方法時最終是調用OkHttpCall內部用ServiceMethod.callFactory創(chuàng)建的okhttp3.Call來執(zhí)行網(wǎng)絡請求。
第三步、調用serviceMethod.callAdapter.adapt(okHttpCall)來產(chǎn)生method所定義的返回
Retrofit2默認支持的返回是返回一個Call<T>,利用此Call<T>實例可執(zhí)行
Response<T> result = call.execute();//同步執(zhí)行
或
//異步執(zhí)行
call.enqueue(new Callback(){
public void onResponse(Call<T> call, Response<T> response){
//TODO
}
public void onFailure(Call<T> call, Throwable t){
//TODO
}
});
其中在Android平臺Retrofit2會自動使用主線程handler構造一個ExecutorCallAdapterFactory,調用enqueue(Callback),callback回調會在主線程中回調
另外在Retrofit的擴展Adapter中還提供了RxJavaCallAdapterFactory,Java8CallAdapterFactory,GuavaCallAdapterFactory
以RxJavaCallAdapterFactory為例,RxJavaCallAdapterFactory創(chuàng)建的callAdapter在執(zhí)行adapt時將OkHttpCall包裝一個Rx的Observable,在Observable被subscribe時才會真正的執(zhí)行http請求。
寫在最后
其實Retrofit已經(jīng)不是一個新庫了,目前版本也是第二個大版本了,本身Retrofit已經(jīng)讓http請求解耦很好了,再加上與Rx的配合,就真的是靈活到不行。我也是從去年下半年才開始關注和研究Retrofit的實現(xiàn),此文章也只是一個淺析,希望能對各位開發(fā)同仁有些幫助吧。