Retrofit原理淺析
做Android也有幾年了急黎,各種Android http類庫也用過不少慰安,自己的做過的項(xiàng)目中也一直在嘗試怎么封裝讓API接口定義和API使用者解耦,但一直感覺沒有可以讓人滿意的框架拧晕,直到無意中在網(wǎng)上看到了JakeWharton大神的Retrofit這個(gè)類庫睛榄。不得不說,真心是炒雞解耦落剪。
下面來簡單分析一下這個(gè)Retrofit2的使用和實(shí)現(xiàn)睁本。
類庫使用示例
首先定義請(qǐng)求接口
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributors(
@Path("owner") String owner,
@Path("repo") String repo);
}
然后通過Retrofit生成一個(gè)剛才定義的接口的實(shí)現(xiàn)類,使用的是動(dòng)態(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);
之后就可以使用接口進(jìn)行請(qǐng)求了
// Create a call instance for looking up Retrofit contributors.
Call<List<Contributor>> call = github.contributors("square", "retrofit");
類庫原理解析
注解
Retrofit使用注解+java接口來定義后臺(tái)服務(wù)API接口
注解主要分為 方法注解 和 參數(shù)注解
注解 | 類型 | 作用 |
---|---|---|
@GET | 方法注解 | 表明HTTP請(qǐng)求方法為GET,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url |
@POST | 方法注解 | 表明HTTP請(qǐng)求方法為POST,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url |
@PUT | 方法注解 | 表明HTTP請(qǐng)求方法為PUT,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url |
@DELETE | 方法注解 | 表明http請(qǐng)求方法為DELETE,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url |
@PATCH | 方法注解 | 表明HTTP請(qǐng)求方法為PATCH,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url |
@HEAD | 方法注解 | 表明HTTP請(qǐng)求方法為HEAD,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url |
@OPTIONS | 方法注解 | 表明HTTP請(qǐng)求方法為OPTIONS,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url |
@HTTP | 方法注解 | 通過@HTTP注解指定http協(xié)議的請(qǐng)求方法,是否允許body,(可選)注解的value屬性用來設(shè)置相對(duì)/絕對(duì)url |
@FormUrlEncoded | 方法注解 | 表明發(fā)起HTTP請(qǐng)求的RequestBody是form表單方式 |
@Multipart | 方法注解 | 表明發(fā)起HTTP請(qǐng)求的RequestBody是Multipar方式 |
@Headers | 方法注解 | 使用注解的value值數(shù)組作為HTTP請(qǐng)求的頭呢堰,用于一些固定的Header參數(shù) |
@Streaming | 方法注解 | 用于需要直接返回流的函數(shù) |
@Url | 參數(shù)注解 | HTTP請(qǐng)求的url路徑(相對(duì)/絕對(duì)),可以包含{path_holder},如:http://xxx.com/{user_holder}/detail |
@Path | 參數(shù)注解 | 用于動(dòng)態(tài)替換URL路徑中的path_holder |
@Body | 參數(shù)注解 | 表明此參數(shù)用作HTTP請(qǐng)求的body |
@Field | 參數(shù)注解 | 表明此參數(shù)用作HTTP請(qǐng)求的form表單參數(shù),key為注解的value值 |
@FieldMap | 參數(shù)注解 | 以map形式傳入的form表單參數(shù) |
@Header | 參數(shù)注解 | 表明此參數(shù)用作HTTP請(qǐng)求的header凡泣,key為注解的value值 |
@HeaderMap | 參數(shù)注解 | 以map形式傳入的多個(gè)header鍵值對(duì) |
@Part | 參數(shù)注解 | 表明參數(shù)為Http的multipart參數(shù)之一 |
@PartMap | 參數(shù)注解 | 以map形式傳入的multipart參數(shù)表 |
@Query | 參數(shù)注解 | GET方法的query參數(shù)枉疼,用于拼接完整請(qǐng)求路徑 |
@QueryMap | 參數(shù)注解 | 以map傳入的GET方法的query參數(shù)皮假,用于拼接完整請(qǐng)求路徑 |
生成動(dòng)態(tài)代理實(shí)例
Retrofit使用的關(guān)鍵一步就是Retrofit.create函數(shù)創(chuàng)建接口動(dò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);
}
});
}
可以看到是為接口的每個(gè)method創(chuàng)建了一個(gè)對(duì)應(yīng)的ServiceMethod,并使用這個(gè)ServiceMethod對(duì)象創(chuàng)建OkHttpCall,并使用ServiceMethod實(shí)例的callAdapter來調(diào)用okhttpCall并返回結(jié)果骂维。
調(diào)用流程
通過上面代碼可以看到調(diào)用關(guān)鍵的就是三步:
- 1 加載對(duì)應(yīng)method的ServiceMethod實(shí)例
- 2 使用ServiceMethod實(shí)例和方法調(diào)用參數(shù)創(chuàng)建OkHttpCall
- 3 調(diào)用serviceMethod.callAdapter.adapt(okHttpCall)來產(chǎn)生method所定義的返回(Call<T>或者其他自定義CallAdapter支持的返回)
第一步惹资、加載對(duì)應(yīng)method的ServiceMethod實(shí)例
ServiceMethod中有以下四個(gè)變量比較重要
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中設(shè)置航闺,如果不設(shè)置褪测,默認(rèn)會(huì)new一個(gè)OkHttpClient作為callFactory
- callAdapter是用來最終處理OkHttpCall實(shí)例并返回接口Method所定義的返回
- responseConverter 用來將Http請(qǐng)求的結(jié)果轉(zhuǎn)換成接口Method所定義的結(jié)果(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對(duì)應(yīng)的ServiceMethod實(shí)例緩存,如果沒有潦刃,則創(chuàng)建一個(gè)該method對(duì)應(yīng)的ServiceMethod實(shí)例并保存到緩存中侮措。
ServiceMethod的創(chuàng)建使用的是建造者模式。
在ServiceMethod.Builder的build方法中乖杠,通過解析傳入的method的方法定義(參數(shù)類型萝毛,返回類型,參數(shù)注解滑黔,方法注解)生成對(duì)應(yīng)的callAdapter,responseConverter,parameterHandlers及其他一些創(chuàng)建請(qǐng)求需要用到的信息。
public ServiceMethod build() {
callAdapter = createCallAdapter();
......檢查返回結(jié)果類型......
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()過程在生成過程中還會(huì)對(duì)method的定義做合法性檢查环揽,如:http方法是get就不允許方法參數(shù)中有body類型的參數(shù)略荡;方法為post則必須有參數(shù)為Body類型。
第二步歉胶、使用ServiceMethod實(shí)例和方法調(diào)用參數(shù)創(chuàng)建OkHttpCall
獲取到method對(duì)應(yīng)的ServiceMethod實(shí)例后汛兜,會(huì)使用該ServiceMethod實(shí)例和方法調(diào)用的參數(shù)Object... args生成一個(gè)OkHttpCall。而OkHttpCall實(shí)際上是okhttp3.Call的一個(gè)包裝類通今,實(shí)際調(diào)用OkHttpCall的相關(guān)執(zhí)行方法時(shí)最終是調(diào)用OkHttpCall內(nèi)部用ServiceMethod.callFactory創(chuàng)建的okhttp3.Call來執(zhí)行網(wǎng)絡(luò)請(qǐng)求粥谬。
第三步、調(diào)用serviceMethod.callAdapter.adapt(okHttpCall)來產(chǎn)生method所定義的返回
Retrofit2默認(rèn)支持的返回是返回一個(gè)Call<T>,利用此Call<T>實(shí)例可執(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平臺(tái)Retrofit2會(huì)自動(dòng)使用主線程handler構(gòu)造一個(gè)ExecutorCallAdapterFactory辫塌,調(diào)用enqueue(Callback)漏策,callback回調(diào)會(huì)在主線程中回調(diào)
另外在Retrofit的擴(kuò)展Adapter中還提供了RxJavaCallAdapterFactory,Java8CallAdapterFactory,GuavaCallAdapterFactory
以RxJavaCallAdapterFactory為例,RxJavaCallAdapterFactory創(chuàng)建的callAdapter在執(zhí)行adapt時(shí)將OkHttpCall包裝一個(gè)Rx的Observable臼氨,在Observable被subscribe時(shí)才會(huì)真正的執(zhí)行http請(qǐng)求掺喻。
寫在最后
其實(shí)Retrofit已經(jīng)不是一個(gè)新庫了,目前版本也是第二個(gè)大版本了储矩,本身Retrofit已經(jīng)讓http請(qǐng)求解耦很好了感耙,再加上與Rx的配合,就真的是靈活到不行持隧。我也是從去年下半年才開始關(guān)注和研究Retrofit的實(shí)現(xiàn)即硼,此文章也只是一個(gè)淺析,希望能對(duì)各位開發(fā)同仁有些幫助吧屡拨。