在之前的文章 《Andriod 網(wǎng)絡框架 OkHttp 源碼解析》 中我們分析了 OkHttp 的源代碼。現(xiàn)在我們就來分析一下 OkHttp 的兄弟框架 Retrofit居灯。關于 Retrofit 的注解的使用德挣,可以參考其官方文檔:https://square.github.io/retrofit/不撑。
Retrofit 也是 Square 發(fā)布的一個開源的庫,它是一個類型安全的 Http 客戶端邻吭,適用于 Android 和 Java餐弱。本質上,Retrofit 使用了 Java 的動態(tài)代理囱晴,內部使用 OkHttp 來進行網(wǎng)絡訪問膏蚓,并且可以通過指定 “請求適配器” 和 “類型轉換器” 來完成:請求的適配,方法參數(shù)到 OkHttp 請求的轉換畸写,以及響應到 Java 類型的轉換驮瞧。
1、基本使用
Retrofit 設計的一個好的地方就是它把我們上面提到的 “請求適配器” 和 “類型轉換器” 使用策略模式解耦出來枯芬。用戶可以根據(jù)自己的需求通過實現(xiàn)指定的接口來自定義自己的類型轉換器论笔。所以,當我們使用 Gson 進行轉換和 RxJava2 進行適配的時候千所,就需要指定下面三個依賴:
api 'com.squareup.retrofit2:retrofit:2.4.0'
api 'com.squareup.retrofit2:converter-gson:2.4.0'
api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
然后狂魔,我們需要根據(jù)自己的 API 接口的信息,在代碼里用一個接口來對該 API 進行聲明:
public interface WXInfoService {
@GET("/sns/userinfo")
Observable<WXUserInfo> getWXUserInfo(
@Query("access_token") String accessToken, @Query("openid") String openId);
}
這里的 WXUserInfo
是由該 API 接口返回的 Json 生成的 Java 對象淫痰。然后毅臊,我們可以像下面這樣獲取一個該接口的代理對象:
WXInfoService wXInfoService = new Retrofit.Builder()
.baseUrl("https://api.weixin.qq.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build().create(WXInfoService.class);
然后,我們就可以使用該對象并調用其方法來獲取接口返回的信息了:
Disposable disposable = wxInfoService.getWXUserInfo(accessToken, openId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(wxUserInfo -> { /*...拿到結果之后進行處理...*/ });
上面我們只使用了 Retrofit 最基礎的 GET
接口黑界。當然,Retrofit 本身的功能遠比這要豐富得多皂林,關于其更多的使用朗鸠,可以參考其官方的文檔。
2础倍、動態(tài)代理:魔力發(fā)生的地方
上面我們使用 Retrofit 進行網(wǎng)絡請求烛占,實際其內部使用 OkHttp 來完成網(wǎng)絡請求的。僅定義了一個接口并調用了該接口的方法,就拿到了請求的結果忆家,這看上去非常簡潔犹菇,而這其中的功不可沒的就是動態(tài)代理。
當我們使用 Retrofit.Builder
的 create()
方法獲取一個 WXInfoService
實例的時候芽卿,實際返回的是經(jīng)過代理之后的對象揭芍。該方法內部會調用 Proxy
的靜態(tài)方法 newProxyInstance()
來得到一個代理之后的實例。為了說明這個方法的作用卸例,我們寫了一個例子:
public static void main(String...args) {
Service service = getProxy(Service.class);
String aJson = service.getAInfo();
System.out.println(aJson);
String bJson = service.getBInfo();
System.out.println(bJson);
}
private static <T> T getProxy(final Class<T> service) {
InvocationHandler h = (proxy, method, args) -> {
String json = "{}";
if (method.getName().equals("getAInfo")) {
json = "{A請求的結果}";
} else if (method.getName().equals("getBInfo")) {
json = "{B請求的結果}";
}
return json;
};
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, h);
}
該程序的輸出結果:
{A請求的結果}
{B請求的結果}
在上面的這個例子中称杨,我們先使用 getProxy()
獲取一個代理之后的實例,然后依次調用它的 getAInfo()
和 getBInfo()
方法筷转,來模擬調用 A 接口和 B 接口的情形姑原,并依次得到了 A 請求的結果和 B 請求的結果。
上面的效果近似于我們使用 Retrofit 訪問接口的過程呜舒。為了說明這個過程中發(fā)生了什么锭汛,我們需要先了解一下這里的 newProxyInstance()
方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
// ...
}
該方法接收三個參數(shù):
- 第一個是類加載器;
- 第二個是接口的 Class 類型袭蝗;
- 第三個是一個處理器唤殴,你可以將其看作一個用于回調的接口。當我們的代理實例觸發(fā)了某個方法的時候呻袭,就會觸發(fā)該回調接口的方法眨八。
InvocationHandler
是一個接口,它內部定義了一個方法如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
該方法也接收三個參數(shù)左电,第一個是觸發(fā)該方法的代理實例廉侧;第二個是觸發(fā)的方法;第三個是觸發(fā)的方法的參數(shù)篓足。invoke()
方法的返回結果會作為代理類的方法執(zhí)行的結果段誊。
所以,當了解了 newProxyInstance()
方法的定義之后栈拖,我們可以做如下總結:當我們使用 newProxyInstance()
方法獲取了一個代理實例 service
并調用其 getAInfo()
方法之后连舍,該方法的信息和參數(shù)信息會分別通過 method
和 args
傳入到 h
的 invoke()
中。所以涩哟,最終的效果就是索赏,當我們調用 service
的 getAInfo()
時候會觸發(fā) h
的 invoke()
。在 invoke()
方法中我們根據(jù) method
得知觸發(fā)的方法是 getAInfo
贴彼。于是潜腻,我們把它對應的請求從 invoke()
方法中返回,并作為 service.getAInfo()
的返回結果器仗。
所以融涣,我們可以總結 Retrofit 的大致工作流程:當我們獲取了接口的代理實例童番,并調用它的 getWXUserInfo()
方法之后,該方法的請求參數(shù)會傳遞到代理類的 InvocationHandler.invoke()
方法中威鹿。然后在該方法中剃斧,我們將這些信息轉換成 OkHttp 的 Request
并使用 OkHttp 進行訪問。從網(wǎng)絡中拿到結果之后忽你,我們使用 “轉換器” 將響應轉換成接口指定的 Java 類型幼东。
上面是 Retrofit 請求處理的基本流程,下面我們看一下 Retrofit 的代理方法內部究竟發(fā)生了什么檀夹。
3筋粗、Retrofit 的源碼解析
3.1 創(chuàng)建 Retrofit
根據(jù)上面的例子,當使用 Retrofit 的時候炸渡,首先我們需要使用 Retrofit 的構建者來創(chuàng)建 Retrofit 的實例娜亿。這里的構建者有幾個重要的方法需要提及一下:
3.1.1 addConverterFactory 方法
該方法用來向 Retrofit 中添加一個 Converter.Factory
。Converter.Factory
蚌堵,顧名思義是一種工廠模式买决。它是一個接口需要,實現(xiàn)兩個重要的方法吼畏。每個方法需要返回一個轉換器:某種數(shù)據(jù)類型到請求體的轉換器督赤,響應體到我們需要的數(shù)據(jù)類型的轉換器。當我們使用 Gson 來完成這個轉換泻蚊,那么我們就需要使用 GsonConverterFactory.create()
來得到一個適用于 Gson 的 Converter.Factory
躲舌。
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
3.1.2 addCallAdapterFactory 方法
CallAdapter.Factory
用于獲取 CallAdapter
對象, CallAdapter
對象用于把原生的 OkHttp 的 Call
轉換成我們指定的請求類型性雄。比如没卸,上面的例子中,我們用來將其轉換成 Observable<WXUserInfo>
秒旋。下面是該方法的定義:
public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
callAdapterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
3.1.3 build 方法
當根據(jù)用戶的自定義設置完了參數(shù)之后约计,就可以調用 build()
方法,來獲取一個 Retrofit 的實例迁筛。在該方法中會將上述方法傳入的 “適配器” 和 “轉換器” 添加到各自的列表中煤蚌,然后 new
出一個 Retrofit
的實例并返回。
3.1.4 小結
為了說明適配器 CallAdapter
和轉換器 Converter
的作用细卧,我們繪制了下圖:
從上面我們看出尉桩,CallAdapter
主要用來將某個請求轉換成我們指定的類型。比如贪庙,在我們最開始的例子中魄健,要將請求轉換成 Observable<WXUserInfo>
。如果轉換之后的請求是 Observable
類型的插勤,那么當我們對轉換后的請求進行訂閱的時候,就啟動了 OkHttp 的網(wǎng)絡請求過程。
在進行網(wǎng)絡請求之前會先使用 Converter
將請求的參數(shù)轉換成一個 RequestBody
农尖。這里將其作為一個接口的好處是便于解耦析恋。比如,上面我們用 Gson 來完成轉換過程盛卡,你也可以通過自定義轉換器來使用其他的框架助隧,比如 Moshi 等。當拿到了響應之后滑沧,我們又會再次使用 Converter
來將響應體 ResponseBody
轉換成我們要求的類型并村。比如 WXUserInfo
。
從上面我們看出滓技,Retrofit 設計的非常妙的地方就在于上面的兩個過程的解耦(策略模式+工廠模式+適配器模式)哩牍。一次是將請求轉換成 Observable
的過程,一次是將請求體和響應體轉換成 OkHttp 要求的 RequestBody
和 ResponseBody
的過程令漂。對于前者膝昆,不論我們使用的是 RxJava 1 還是 RxJava 2,只要傳入一個 CallAdapter
即可叠必。對于后者荚孵,不論我們使用哪種 Json 轉換框架,只要實現(xiàn)了 Converter
接口皆可纬朝。
3.2 獲取代理實例
3.2.1 劃分平臺:Platform
創(chuàng)建了 Retrofit 的實例之后收叶,我們就可以使用它的 create()
方法來獲取代理之后的服務實例。下面是這個方法的定義共苛。在這里判没,我們會先根據(jù) validateEagerly
變量來判斷是否立即對傳入的服務接口的方法進行解析。然后俄讹,我們使用 Proxy
的靜態(tài)方法獲取一個代理實例哆致。
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
// 這里的 validateEagerly 在 Retrofit 構建的時候設置
if (validateEagerly) {
// 是否立即對 Service 方法的內容進行解析
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, @Nullable Object[] args)
throws Throwable {
// 該方法是 Object 的方法,直接觸發(fā)該方法
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
// 如果是 default 方法患膛,那么使用該 Java8 平臺的方法執(zhí)行
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
// 獲取服務方法的信息摊阀,并將其包裝成 ServiceMethod
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
}
});
}
這里的 eagerlyValidateMethods()
方法定義如下:
private void eagerlyValidateMethods(Class<?> service) {
// 獲取程序當前運行的平臺
Platform platform = Platform.get();
for (Method method : service.getDeclaredMethods()) {
// 判斷該方法是否是 default 方法
if (!platform.isDefaultMethod(method)) {
loadServiceMethod(method);
}
}
}
它的作用是立即對服務接口的方法進行解析,并將解析之后的結果放進一個緩存中踪蹬。這樣胞此,當這個服務方法被觸發(fā)的時候,直接從緩存當中獲取解析之后的 ServiceMethod
來使用即可跃捣。該方法會先會根據(jù)當前程序運行的平臺來決定是否應該加載服務的方法漱牵。因為,Java 8 之后疚漆,我們可以為接口增加 default
類型的方法酣胀,所以刁赦,如果是 default
類型的話,我們不會調用 loadServiceMethod()
進行解析闻镶,而是調用 Java8 平臺的 invokeDefaultMethod()
來處理甚脉。在 invokeDefaultMethod()
中,會根據(jù)傳入的信息創(chuàng)建一個實例并使用反射觸發(fā)它的方法。此時,就間接地觸發(fā)了該 default
方法蜒滩。
判斷平臺的時候善涨,使用了如下這段代碼:
platform.isDefaultMethod(Method)
這里的 platform 是調用 Platform.get()
的時候得到的。它會在 get()
方法中嘗試使用反射去獲取一個只有 Java8 平臺才具有的類,以此來判斷是否是 Java8 的環(huán)境。在 Retrofit 中,提供了 Java8
和 Android
兩個類來區(qū)分所在的平臺郊霎,并會根據(jù)運行環(huán)境來決定返回哪個實例。
所以蒲障,Platform 應用了策略模式歹篓,以對不同的平臺做不同的處理。在當前的版本中揉阎,它的主要作用是對 default
類型的方法進行處理庄撮。
3.2.2 解析服務方法:ServiceMethod
上面我們提到過 loadServiceMethod()
方法,它的主要作用:首先會嘗試從緩存當中獲取該方法對應的 ServiceMethod
實例毙籽,如果取到的話洞斯,就將其返回;否則坑赡,就使用構建者模式創(chuàng)建一個并放進緩存中烙如,然后將其返回。
ServiceMethod<?, ?> loadServiceMethod(Method method) {
// 從緩存中進行獲取
ServiceMethod<?, ?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
// 創(chuàng)建ServiceMethod實例
result = new ServiceMethod.Builder<>(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
ServiceMethod
的構建過程比較簡單毅否,只需要把當前的 Retrofit
實例和服務方法 method
傳入進去亚铁,然后調用它的 build()
方法就完成了整個創(chuàng)建過程。在 build()
方法中螟加,會完成對 method
的解析徘溢,比如根據(jù)注解判斷是什么類型的請求,根據(jù)方法的參數(shù)來解析請求的請求體等等捆探。
所以然爆,ServiceMethod
的作用是緩存服務方法對應的請求信息,這樣下次我們就不需要再次解析了黍图。同時曾雕,它提供了以下幾個方法。它們的主要作用是用來從 ServiceMethod
中獲取請求相關的信息:
toCall()
用來獲取用于 OkHttp 請求的 Call
對象:
okhttp3.Call toCall(@Nullable Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
contentType, hasBody, isFormEncoded, isMultipart);
// ...
return callFactory.newCall(requestBuilder.build());
}
toResponse(ResponseBody)
用來把 OkHttp 得到的響應體轉換成 Java 對象等(在示例中是WXUserInfo
):
R toResponse(ResponseBody body) throws IOException {
return responseConverter.convert(body);
}
adapt(Call<R>)
用來將 OkHttp 的請求轉換成我們的服務方法的返回類型(在示例中是Observable<WXUserInfo>
):
T adapt(Call<R> call) {
return callAdapter.adapt(call);
}
3.2.3 請求封裝:OkHttpCall
解析完畢服務方法之后助被,我們得到了 ServiceMethod
實例剖张。然后切诀,我們使用它來創(chuàng)建 OkHttpCall
實例。這里的 OkHttpCall
實現(xiàn)了 Retrofit 中定義的 Call
接口搔弄,會在方法內調用 ServiceMethod
的 toCall()
方法來獲取 OkHttp 中的 Call
對象趾牧,然后使用它進行網(wǎng)絡訪問。當拿到了請求的結果之后又使用 ServiceMethod
的 toResponse()
把響應轉換成我們指定的類型肯污。下面是該類中的幾個比較重要的方法:
-
execute()
方法,用來同步執(zhí)行網(wǎng)絡請求:
@Override
public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
// ...
call = rawCall;
if (call == null) {
try {
// 創(chuàng)建 OkHttp 的 Call 實例
call = rawCall = createRawCall();
} catch (IOException | RuntimeException | Error e) {
throwIfFatal(e);
creationFailure = e;
throw e;
}
}
}
if (canceled) {
call.cancel();
}
// 同步執(zhí)行請求吨枉,并解析結果
return parseResponse(call.execute());
}
-
createRawCall()
用來創(chuàng)建 OkHttp 的Call
實例:
// 使用 serviceMethod 的 toCall 方法獲取 OkHttp 的 Call 實例
private okhttp3.Call createRawCall() throws IOException {
okhttp3.Call call = serviceMethod.toCall(args);
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
-
parseResponse()
用來將 OkHttp 的響應轉換成我們接口中定義的類型蹦渣。比如,在我們的例子中貌亭,返回的是Observable<WXUserInfo>
:
// 使用 serviceMethod 的 toResponse 方法獲取 OkHttp 的 Response 實例
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
rawResponse = rawResponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
// ...
ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
try {
// 使用 serviceMethod 的 toResponse 方法獲取 OkHttp 的 Response 實例
T body = serviceMethod.toResponse(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
catchingBody.throwIfCaught();
throw e;
}
}
3.3 Retrofit 的工作過程
上面是 Retrofit 框架設計中幾個關鍵的部分的功能的解析柬唯。下面,我們再來具體看一下圃庭,從觸發(fā)代理類的方法到拿到響應的結果锄奢,這一整個過程中,都有哪些類的哪些方法參與剧腻,以及它們在什么時候拘央,扮演什么樣的角色。這里我們仍然使用最初的示例:
上圖中书在,我們將 Retrofit 的請求的過程分成三個過程來進行說明:
-
創(chuàng)建代理實例的過程:在這個過程中主要是調用
Proxy.newProxyInstance()
來獲取一個代理實例灰伟。相關的主要參數(shù)是validateEagerly
,我們會使用它來決定是否立即對傳入的接口的方法進行解析儒旬。不論我們什么時候進行解析栏账,都會把解析的結果緩存起來。 -
觸發(fā)代理方法的過程:觸發(fā)代理方法是整個請求的第二過程栈源。這個時候挡爵,我們調用了
WXInfoService
代理實例的getWXUserInfo()
方法。此時甚垦,會觸發(fā)InvocationHandler.invoke()
方法茶鹃。在該方法內部會調用ServiceMethod
的構建者模式來創(chuàng)建serviceMethod
實例。當調用構建者模式的build()
方法的時候制轰,會對方法getWXUserInfo()
的信息進行解析前计。然后,使用serviceMethod
創(chuàng)建okHttpCall
垃杖。最后男杈,調用serviceMethod.adapt()
方法將okHttpCall
實例轉換成Observable<WXUserInfo>
。在轉換的過程中會使用CallAdapter
的adapt()
方法來完成適配调俘。 -
執(zhí)行網(wǎng)絡請求的過程:拿到了
Observable<WXUserInfo>
之后伶棒,需要對其進行訂閱才能觸發(fā)網(wǎng)絡請求旺垒。相關的邏輯在CallAdapter
中完成。首先肤无,它會根據(jù)你使用同步還是異步的來決定使用哪個執(zhí)行器先蒋。這里存在兩個執(zhí)行器,它們的區(qū)別是一個會在內部調用OkHttpCall
的enqueue()
宛渐,另一個會在執(zhí)行器中調用OkHttpCall
的execute()
方法竞漾。不論調用enqueue()
還是execute()
,都會先使用OkHttpCall
的toCall()
方法獲取一個Call
請求窥翩。獲取請求的過程中會使用Converter
來將某個實例轉換成請求體业岁。拿到了請求之后,使用該請求來進行網(wǎng)絡訪問寇蚊。當從網(wǎng)絡中拿到了響應之后笔时,會使用Converter
來將響應體轉換成對象。這樣仗岸,拿到了實際的結果之后允耿,就會調用Observer
的onNext()
方法把結果通知給觀察者。
4扒怖、總結
在這篇文章中较锡,我們先簡單介紹了 Retrofit 的使用,然后姚垃,因為 Retrofit 內部使用動態(tài)代理來實現(xiàn)的念链,所以,我們對動態(tài)代理相關內容進行了介紹积糯。最后掂墓,我們對 Retrofit 的源碼進行了分析,先從設計思路看成,后從各個環(huán)節(jié)的執(zhí)行過程進行了說明君编。最后的最后,我們將兩者結合起來用一個時序圖做了說明川慌。
從上文中可以看出來吃嘿,Retrofit 設計的幾個值得我們借鑒的地方:
- 使用運行時注解和反射簡化請求描述,但是考慮到反射的效率比較低梦重,所以將一次反射之后的結果緩存起來兑燥,以便于下次使用。
- 動態(tài)代理:使用接口描述請求的好處是它簡潔琴拧,而且 “描述” 本來就是它的責任降瞳。但是,一般我們需要去實現(xiàn)接口才能使用。而這里告訴我們挣饥,使用動態(tài)代理一樣可以使用接除师。
- 解耦:從我們上面的圖中也可以看出來,Retrofit 的設計的思路是比較清晰的扔枫。它將一個請求的幾個過程解耦出來汛聚。首先是我們
Observable
到請求的轉換,這里使用適配器來完成短荐;然后是請求體和響應體的轉換倚舀,基本就是 Json 的轉換,使用轉換器來完成忍宋。這樣瞄桨,不論你使用 RxJava 1 還是 RxJava 2,不論是 Gson 還是 FastXml 都可以和 Retrifut 配合使用讶踪。
以上就是我們對 Retrofit 的源碼的分析。