動態代理在 Android 中的應用:Retrofit 源碼解析
在之前的文章 《Andriod 網絡框架 OkHttp 源碼解析》 中我們分析了 OkHttp 的源代碼。現在我們就來分析一下 OkHttp 的兄弟框架 Retrofit。關于 Retrofit 的注解的使用,可以參考其官方文檔:https://square.github.io/retrofit/。
Retrofit 也是 Square 發布的一個開源的庫,它是一個類型安全的 Http 客戶端,適用于 Android 和 Java。本質上,Retrofit 使用了 Java 的動態代理,內部使用 OkHttp 來進行網絡訪問,并且可以通過指定 “請求適配器” 和 “類型轉換器” 來完成:請求的適配,方法參數到 OkHttp 請求的轉換,以及響應到 Java 類型的轉換。
1、基本使用
Retrofit 設計的一個好的地方就是它把我們上面提到的 “請求適配器” 和 “類型轉換器” 使用策略模式解耦出來。用戶可以根據自己的需求通過實現指定的接口來自定義自己的類型轉換器。所以,當我們使用 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'
然后,我們需要根據自己的 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、動態代理:魔力發生的地方
上面我們使用 Retrofit 進行網絡請求,實際其內部使用 OkHttp 來完成網絡請求的。僅定義了一個接口并調用了該接口的方法,就拿到了請求的結果,這看上去非常簡潔,而這其中的功不可沒的就是動態代理。
當我們使用 Retrofit.Builder
的 create()
方法獲取一個 WXInfoService
實例的時候,實際返回的是經過代理之后的對象。該方法內部會調用 Proxy
的靜態方法 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 訪問接口的過程。為了說明這個過程中發生了什么,我們需要先了解一下這里的 newProxyInstance()
方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {// ...}
該方法接收三個參數:
- 第一個是類加載器;
- 第二個是接口的 Class 類型;
- 第三個是一個處理器,你可以將其看作一個用于回調的接口。當我們的代理實例觸發了某個方法的時候,就會觸發該回調接口的方法。
InvocationHandler
是一個接口,它內部定義了一個方法如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
該方法也接收三個參數,第一個是觸發該方法的代理實例;第二個是觸發的方法;第三個是觸發的方法的參數。invoke()
方法的返回結果會作為代理類的方法執行的結果。
所以,當了解了 newProxyInstance()
方法的定義之后,我們可以做如下總結:當我們使用 newProxyInstance()
方法獲取了一個代理實例 service 并調用其 getAInfo()
方法之后,該方法的信息和參數信息會分別通過 method 和 args 傳入到 h 的 invoke()
中。所以,最終的效果就是,當我們調用 service 的 getAInfo()
時候會觸發 h 的 invoke()
。在 invoke()
方法中我們根據 method 得知觸發的方法是 getAInfo
。于是,我們把它對應的請求從 invoke()
方法中返回,并作為 service.getAInfo()
的返回結果。
所以,我們可以總結 Retrofit 的大致工作流程:當我們獲取了接口的代理實例,并調用它的 getWXUserInfo()
方法之后,該方法的請求參數會傳遞到代理類的 InvocationHandler.invoke()
方法中。然后在該方法中,我們將這些信息轉換成 OkHttp 的 Request 并使用 OkHttp 進行訪問。從網絡中拿到結果之后,我們使用 “轉換器” 將響應轉換成接口指定的 Java 類型。
上面是 Retrofit 請求處理的基本流程,下面我們看一下 Retrofit 的代理方法內部究竟發生了什么。
3、Retrofit 的源碼解析
3.1 創建 Retrofit
根據上面的例子,當使用 Retrofit 的時候,首先我們需要使用 Retrofit 的構建者來創建 Retrofit 的實例。這里的構建者有幾個重要的方法需要提及一下:
3.1.1 addConverterFactory 方法
該方法用來向 Retrofit 中添加一個 Converter.Factory
。Converter.Factory
,顧名思義是一種工廠模式。它是一個接口需要,實現兩個重要的方法。每個方法需要返回一個轉換器:某種數據類型到請求體的轉換器,響應體到我們需要的數據類型的轉換器。當我們使用 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 方法
當根據用戶的自定義設置完了參數之后,就可以調用 build()
方法,來獲取一個 Retrofit 的實例。在該方法中會將上述方法傳入的 “適配器” 和 “轉換器” 添加到各自的列表中,然后 new
出一個 Retrofit
的實例并返回。
3.1.4 小結
為了說明適配器 CallAdapter
和轉換器 Converter
的作用,我們繪制了下圖:
從上面我們看出,CallAdapter
主要用來將某個請求轉換成我們指定的類型。比如,在我們最開始的例子中,要將請求轉換成 Observable<WXUserInfo>
。如果轉換之后的請求是 Observable
類型的,那么當我們對轉換后的請求進行訂閱的時候,就啟動了 OkHttp 的網絡請求過程。
在進行網絡請求之前會先使用 Converter
將請求的參數轉換成一個 RequestBody
。這里將其作為一個接口的好處是便于解耦。比如,上面我們用 Gson 來完成轉換過程,你也可以通過自定義轉換器來使用其他的框架,比如 Moshi 等。當拿到了響應之后,我們又會再次使用 Converter
來將響應體 ResponseBody
轉換成我們要求的類型。比如 WXUserInfo
。
從上面我們看出,Retrofit 設計的非常妙的地方就在于上面的兩個過程的解耦(策略模式+工廠模式+適配器模式)。一次是將請求轉換成 Observable
的過程,一次是將請求體和響應體轉換成 OkHttp 要求的 RequestBody
和 ResponseBody
的過程。對于前者,不論我們使用的是 RxJava 1 還是 RxJava 2,只要傳入一個 CallAdapter
即可。對于后者,不論我們使用哪種 Json 轉換框架,只要實現了 Converter
接口皆可。
3.2 獲取代理實例
3.2.1 劃分平臺:Platform
創建了 Retrofit 的實例之后,我們就可以使用它的 create()
方法來獲取代理之后的服務實例。下面是這個方法的定義。在這里,我們會先根據 validateEagerly
變量來判斷是否立即對傳入的服務接口的方法進行解析。然后,我們使用 Proxy
的靜態方法獲取一個代理實例。
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 的方法,直接觸發該方法if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args);}// 如果是 default 方法,那么使用該 Java8 平臺的方法執行if (platform.isDefaultMethod(method)) {return platform.invokeDefaultMethod(method, service, proxy, args);}// 獲取服務方法的信息,并將其包裝成 ServiceMethodServiceMethod<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);}}
}
它的作用是立即對服務接口的方法進行解析,并將解析之后的結果放進一個緩存中。這樣,當這個服務方法被觸發的時候,直接從緩存當中獲取解析之后的 ServiceMethod
來使用即可。該方法會先會根據當前程序運行的平臺來決定是否應該加載服務的方法。因為,Java 8 之后,我們可以為接口增加 default
類型的方法,所以,如果是 default
類型的話,我們不會調用 loadServiceMethod()
進行解析,而是調用 Java8 平臺的 invokeDefaultMethod()
來處理。在 invokeDefaultMethod()
中,會根據傳入的信息創建一個實例并使用反射觸發它的方法。此時,就間接地觸發了該 default
方法。
判斷平臺的時候,使用了如下這段代碼:
platform.isDefaultMethod(Method)
這里的 platform 是調用 Platform.get()
的時候得到的。它會在 get()
方法中嘗試使用反射去獲取一個只有 Java8 平臺才具有的類,以此來判斷是否是 Java8 的環境。在 Retrofit 中,提供了 Java8
和 Android
兩個類來區分所在的平臺,并會根據運行環境來決定返回哪個實例。
所以,Platform 應用了策略模式,以對不同的平臺做不同的處理。在當前的版本中,它的主要作用是對 default
類型的方法進行處理。
3.2.2 解析服務方法:ServiceMethod
上面我們提到過 loadServiceMethod()
方法,它的主要作用:首先會嘗試從緩存當中獲取該方法對應的 ServiceMethod
實例,如果取到的話,就將其返回;否則,就使用構建者模式創建一個并放進緩存中,然后將其返回。
ServiceMethod<?, ?> loadServiceMethod(Method method) {// 從緩存中進行獲取ServiceMethod<?, ?> result = serviceMethodCache.get(method);if (result != null) return result;synchronized (serviceMethodCache) {result = serviceMethodCache.get(method);if (result == null) {// 創建ServiceMethod實例result = new ServiceMethod.Builder<>(this, method).build();serviceMethodCache.put(method, result);}}return result;
}
ServiceMethod
的構建過程比較簡單,只需要把當前的 Retrofit
實例和服務方法 method
傳入進去,然后調用它的 build()
方法就完成了整個創建過程。在 build()
方法中,會完成對 method
的解析,比如根據注解判斷是什么類型的請求,根據方法的參數來解析請求的請求體等等。
所以,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
實例。然后,我們使用它來創建 OkHttpCall
實例。這里的 OkHttpCall
實現了 Retrofit 中定義的 Call
接口,會在方法內調用 ServiceMethod
的 toCall()
方法來獲取 OkHttp 中的 Call
對象,然后使用它進行網絡訪問。當拿到了請求的結果之后又使用 ServiceMethod
的 toResponse()
把響應轉換成我們指定的類型。下面是該類中的幾個比較重要的方法:
execute()
方法,用來同步執行網絡請求:
@Overridepublic Response<T> execute() throws IOException {okhttp3.Call call;synchronized (this) {// ...call = rawCall;if (call == null) {try {// 創建 OkHttp 的 Call 實例call = rawCall = createRawCall();} catch (IOException | RuntimeException | Error e) {throwIfFatal(e);creationFailure = e;throw e;}}}if (canceled) {call.cancel();}// 同步執行請求,并解析結果return parseResponse(call.execute());}
createRawCall()
用來創建 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 框架設計中幾個關鍵的部分的功能的解析。下面,我們再來具體看一下,從觸發代理類的方法到拿到響應的結果,這一整個過程中,都有哪些類的哪些方法參與,以及它們在什么時候,扮演什么樣的角色。這里我們仍然使用最初的示例:
上圖中,我們將 Retrofit 的請求的過程分成三個過程來進行說明:
- 創建代理實例的過程:在這個過程中主要是調用
Proxy.newProxyInstance()
來獲取一個代理實例。相關的主要參數是validateEagerly
,我們會使用它來決定是否立即對傳入的接口的方法進行解析。不論我們什么時候進行解析,都會把解析的結果緩存起來。 - 觸發代理方法的過程:觸發代理方法是整個請求的第二過程。這個時候,我們調用了
WXInfoService
代理實例的getWXUserInfo()
方法。此時,會觸發InvocationHandler.invoke()
方法。在該方法內部會調用ServiceMethod
的構建者模式來創建serviceMethod
實例。當調用構建者模式的build()
方法的時候,會對方法getWXUserInfo()
的信息進行解析。然后,使用serviceMethod
創建okHttpCall
。最后,調用serviceMethod.adapt()
方法將okHttpCall
實例轉換成Observable<WXUserInfo>
。在轉換的過程中會使用CallAdapter
的adapt()
方法來完成適配。 - 執行網絡請求的過程:拿到了
Observable<WXUserInfo>
之后,需要對其進行訂閱才能觸發網絡請求。相關的邏輯在CallAdapter
中完成。首先,它會根據你使用同步還是異步的來決定使用哪個執行器。這里存在兩個執行器,它們的區別是一個會在內部調用OkHttpCall
的enqueue()
,另一個會在執行器中調用OkHttpCall
的execute()
方法。不論調用enqueue()
還是execute()
,都會先使用OkHttpCall
的toCall()
方法獲取一個Call
請求。獲取請求的過程中會使用Converter
來將某個實例轉換成請求體。拿到了請求之后,使用該請求來進行網絡訪問。當從網絡中拿到了響應之后,會使用Converter
來將響應體轉換成對象。這樣,拿到了實際的結果之后,就會調用Observer
的onNext()
方法把結果通知給觀察者。
4、總結
在這篇文章中,我們先簡單介紹了 Retrofit 的使用,然后,因為 Retrofit 內部使用動態代理來實現的,所以,我們對動態代理相關內容進行了介紹。最后,我們對 Retrofit 的源碼進行了分析,先從設計思路,后從各個環節的執行過程進行了說明。最后的最后,我們將兩者結合起來用一個時序圖做了說明。
從上文中可以看出來,Retrofit 設計的幾個值得我們借鑒的地方:
- 使用運行時注解和反射簡化請求描述,但是考慮到反射的效率比較低,所以將一次反射之后的結果緩存起來,以便于下次使用。
- 動態代理:使用接口描述請求的好處是它簡潔,而且 “描述” 本來就是它的責任。但是,一般我們需要去實現接口才能使用。而這里告訴我們,使用動態代理一樣可以使用接。
- 解耦:從我們上面的圖中也可以看出來,Retrofit 的設計的思路是比較清晰的。它將一個請求的幾個過程解耦出來。首先是我們
Observable
到請求的轉換,這里使用適配器來完成;然后是請求體和響應體的轉換,基本就是 Json 的轉換,使用轉換器來完成。這樣,不論你使用 RxJava 1 還是 RxJava 2,不論是 Gson 還是 FastXml 都可以和 Retrifut 配合使用。
以上就是我們對 Retrofit 的源碼的分析。
另外
有什么技術問題歡迎加我交流 qilebeaf
本人10多年大廠軟件開發經驗,精通Android,Java,Python,前端等開發,空余時間承接軟件開發設計、課程設計指導、解決疑難bug、AI大模型搭建,AI繪圖應用等。
歡迎砸單