作者:RainyJiang
在學(xué)習(xí)
Retrofit
后,由于它本身就是OKHttp
的封裝,面試中也經(jīng)常會被一起問到;單純的解析它的源碼學(xué)習(xí)難免會有點(diǎn)無從下手,往往讓人抓不住重點(diǎn),學(xué)習(xí)效率并不是很高,本文從提出幾個問題出發(fā),帶著問題去思考學(xué)習(xí)Retrofit
源碼,從而快速理解它的核心知識點(diǎn)
下面我將從以下幾個問題來梳理Retrofit
的知識體系,方便自己理解
-
Retrofit
中create
為什么使用動態(tài)代理? - 談?wù)?code>Retrofit運(yùn)用的動態(tài)代理及反射?
-
Retrofit
注解是怎么進(jìn)行解析的? -
Retrofit
如何將注解封裝成OKHttp
的Call
? -
Rretrofit
是怎么完成線程切換和數(shù)據(jù)適配的?
Retrofit
中create
為什么使用動態(tài)代理
我們首先可以看Retrofit
代理實(shí)例創(chuàng)建過程,通過一個例子來說明
val retrofit = Retrofit.Builder()
.baseUrl("https://www.baidu.com")
.build()
val myInterface = retrofit.create(MyInterface::class.java)
創(chuàng)建了一個MyInterface
接口類對象,create
函數(shù)內(nèi)使用了動態(tài)代理來創(chuàng)建接口對象,這樣的設(shè)計(jì)可以讓所有的訪問請求都給被代理,這里我簡化了下它的create函數(shù),簡單來說它的作用就是創(chuàng)建了一個你傳入類型的接口實(shí)例
/**
*
* @param loader 需要代理執(zhí)行的接口類
* @return 動態(tài)代理,運(yùn)行的時候生成一個loader對象類型的類,在調(diào)用它的時候走
*/
@SuppressWarnings("unchecked")
public <T> T create(final Class<T> loader) {
return (T) Proxy.newProxyInstance(loader.getClassLoader(),
new Class<?>[]{loader}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("具體方法調(diào)用前的準(zhǔn)備工作");
Object result = method.invoke(object, args);
System.out.println("具體方法調(diào)用后的善后事情");
return result;
}
});
}
那么這個函數(shù)為什么要使用動態(tài)代理呢,這樣有什么好處?
我們進(jìn)行Retrofit
請求的時候構(gòu)建了許多接口,并且都要調(diào)用接口中的對象;接下來我們再調(diào)用這個對象中的getSharedList
方法
val sharedListCall:Call<ShareListBean> = myService.getSharedList(2,1)
在調(diào)用它的時候,在動態(tài)代理里面,在運(yùn)行的時候會存在一個函數(shù)getSharedList
,這個函數(shù)里面會調(diào)用invoke
,這個invoke
函數(shù)就是Retrofit
里的invoke
函數(shù);并且也形成了一個功能攔截,如下圖所示:
所以,相當(dāng)于動態(tài)代理可以代理所有的接口,讓所有的接口都走invoke函數(shù),這樣就可以攔截調(diào)用函數(shù)的值,相當(dāng)于獲取到所有的注解信息,也就是Request
動態(tài)變化內(nèi)容,至此不就可以動態(tài)構(gòu)建帶有具體的請求的URL
了么,從而就可以將網(wǎng)絡(luò)接口的參數(shù)配置歸一化
這樣也就解決了之前OKHttp
存在的接口配置繁瑣問題,既然都是要構(gòu)建Request
,為了自主動態(tài)的來完成,所以Retrofit
使用了動態(tài)代理
談?wù)?code>Retrofit運(yùn)用的動態(tài)代理及反射
那么我們在讀Retrofit
源碼的時候,是否都有這樣一個問題,為什么我寫個接口以及一些接口Api
,我們就可以完成相應(yīng)的http
請求呢?Retrofit
到底在這其中做了什么工作?簡單來說,其核心就是通過反射+動態(tài)代理來解決的,那么動態(tài)代理和反射的原理是怎么樣的?
代理模式梳理
首先我們要明白代理模式到底是怎么樣的,這里我簡單梳理下
- 代理類與委托類有著同樣的接口
- 代理類主要為委托類預(yù)處理消息,過濾消息,然后把消息發(fā)送給委托類,以及事后處理消息等等
- 一個代理類的對象與一個委托類的對象關(guān)聯(lián),代理類的對象本身并不真正實(shí)現(xiàn)服務(wù),而是通過調(diào)用委托類的對象的相關(guān)方法,為提供特定的服務(wù)
以上是普通代理模式(靜態(tài)代理)它是有一個具體的代理類來實(shí)現(xiàn)的
動態(tài)代理+反射
那么Retrofit
用到的動態(tài)代理呢?答案不言而喻,所謂動態(tài)就是沒有一個具體的代理類,我們看到Retrofit
的create
函數(shù)中,它是可以為委托類對象生成代理類, 代理類可以將所有的方法調(diào)用分派到委托對象上反射執(zhí)行,大致如下
- 接口的
classLoader
- 只包含接口的
class
數(shù)組 - 自定義的
InvocationHandler()
對象, 該對象實(shí)現(xiàn)了invoke() 函數(shù), 通常在該函數(shù)中實(shí)現(xiàn)對委托類函數(shù)的訪問
這就是在create
函數(shù)中所使用的動態(tài)代理及反射
擴(kuò)展:通過這些文章,了解更多動態(tài)代理與反射
反射,動態(tài)代理在Retrofit中的運(yùn)用
Retrofit的代理模式解析
Retrofit
注解是怎么進(jìn)行解析的?
在使用Retrofit
的時候,或者定義接口的時候,在接口方法中加入相應(yīng)的注解(@GET
,@POST
,@Query
,@FormUrlEncoded
等),然后我們就可以調(diào)用這些方法進(jìn)行網(wǎng)絡(luò)請求了;那么就有了問題,為什么注解可以完整的覆蓋網(wǎng)絡(luò)請求?我們知道,注解大致分為三類,通過請求方法、請求體編碼格式、請求參數(shù),大致有22種注解,它們基本完整的覆蓋了HTTP
請求方案 ;通過它們我們確定了網(wǎng)絡(luò)請求request的具體方案;
此時,就拋出了開始的問題,Retrofit
注解是怎么被解析的呢?這里就要熟悉Retrofit
中的ServiceMethod
類了,總的來說,它首先選擇Retrofit
里提供的工具(數(shù)據(jù)轉(zhuǎn)換器converter,請求適配器adapter),相當(dāng)于就是具體請求Request
的一個封裝,封裝完成之后將注解進(jìn)行解析;
下面通過官方提供例子來說明下ServiceMethod
組成的部分
其中 @GET("users/{user}/repos"
是由parseMethodAnnotation
負(fù)責(zé)解析的;@Path
參數(shù)注解就由對應(yīng)ParameterHandler
進(jìn)行處理,剩下的Call<List<Repo>
毫無疑問就是使用CallAdapter
將這個Call
類型適配為用戶定義的 service method
的返回類型。
那么ServiceMethod
是怎么對注解進(jìn)行解析的呢,來簡單梳理下它的源碼
- 首先,在
loadService
方法中進(jìn)行檢測,禁止靜態(tài)方法,這里Retrofit
筆者使用的是2.9.0版本,不再是直接調(diào)用ServiceMethod.Builder()
,而是使用緩存的方式調(diào)用ServiceMethod.parseAnnotations(this, method)
,將它轉(zhuǎn)為RequestFactory
對象,其實(shí)本地大同小異,原理是差不多的
final class RequestFactory {
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}
...
- 同樣在
RequestFactory
中也是使用Builder模式,其實(shí)就是封裝了一層,傳入retrofit
-method
兩個參數(shù),在這里面我們調(diào)用了Method
類獲取了它的注解數(shù)組methodAnnotations
,型參的類型數(shù)組parameterTypes
,型參的注解數(shù)組parameterAnnotationsArray
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
- 然后在
build
方法中,它會創(chuàng)建一個ReqeustFactory
對象,最終解析它通過HttpServiceMethod
又轉(zhuǎn)換成ServiceMethod
實(shí)例,這個方法里主要就是針對注解的解析過程,由于源碼非常長,感興趣的同學(xué)可以去詳細(xì)閱讀下,這里大概概括下幾個重要的解析方法
-
parseMethodAnnotation
該方法就是確定網(wǎng)絡(luò)的傳輸方式,判斷加了哪些注解,下面借用一張網(wǎng)絡(luò)上的圖表達(dá)會更直觀點(diǎn)
-
parseHttpMethodAndPath
,parseHeaders
我們通過上圖也可以看到,其實(shí)就是解析
httpMethod
和headers
,它們都是在parseMethodAnnotation
方法中被調(diào)用的,從而進(jìn)行細(xì)化。前者確定的是請求方法(get,post,delete等),后者顧名思義確定的是headers
頭部;前者會檢測httpMethod
,它不允許有多個方法注解,會使用正則表達(dá)式進(jìn)行判斷,url
中的參數(shù)必須用括號占位,最終提取出了真實(shí)的url
和url
中攜帶的參數(shù)名稱;后者就是解析當(dāng)前Http
請求的頭部信息
- 經(jīng)過以上方法注解處理以及驗(yàn)證,在
build
方法中還要對參數(shù)注解進(jìn)行處理
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
parameterHandlers[p] =
parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}
它循環(huán)進(jìn)行參數(shù)的驗(yàn)證處理,通過parseParameter
方法最后一個參數(shù)判斷是否繼續(xù),參考下網(wǎng)絡(luò)上的圖示
簡單說下ParameterHandler
是怎么處理這個參數(shù)注解的?它會通過進(jìn)行兩項(xiàng)校驗(yàn),分別是對不確定的類型合法校驗(yàn)和路徑名稱的校驗(yàn),然后就是一堆參數(shù)注解的處理,分析源碼后可以看到ParameterHandler
最終都組裝成了一個RequestBuilder
,那么它是用來干神馬的?答案是生成OKHttp
的Request
,網(wǎng)絡(luò)請求還是交給OKHttp
來完成
以上簡單分析了下Retrofit
注解的解析過程,需要深入了解的同學(xué)請自行探索。
如果同學(xué)對注解不太熟悉,想要了解
Java注解
的相關(guān)知識點(diǎn)可以閱讀這篇文章--->(Retrofit注解)
Retrofit
如何將注解封裝成OKHttp
的call
上個問題已經(jīng)知道了Retrofit
中的ServiceMethod
對會注解進(jìn)行解析封裝,這時候各種網(wǎng)絡(luò)請求適配器,請求頭,參數(shù),轉(zhuǎn)換器等等都準(zhǔn)備好了,最終它會將ServiceMethod
轉(zhuǎn)為Retrofit
提供的OkHttpCall
,這個就是對okhttp3.Call
的封裝,答案已經(jīng)呼之欲出了。
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
換種說法,相當(dāng)于在ServiceMethod
中已經(jīng)將注解轉(zhuǎn)變?yōu)?code>url +請求頭+參數(shù)等等格式,對照OKHttp
請求流程,是不是已經(jīng)完成了構(gòu)建Request
請求了,它最終要變成Okhttp3.call
才能進(jìn)行網(wǎng)絡(luò)請求,所以OkHttpCall
基本上就是做了這么一件事情,下面有張圖可以直觀看下ServiceMethod
大概做了哪些事情
接著我們看下okhttpCall
中的enqueue
方法,它會去創(chuàng)建一個真實(shí)的Call
,這個其實(shí)就是OKHttp
中的call
,接下來的網(wǎng)絡(luò)請求工作就交給OKHttp
來完成
private okhttp3.Call createRawCall() throws IOException {
okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
到這里,說白了Retrofit
其實(shí)沒有任何與網(wǎng)絡(luò)請求相關(guān)的東西,它最終還是通過統(tǒng)一解析注解Request
去構(gòu)建OkhttpCall
執(zhí)行,通過設(shè)計(jì)模式去封裝執(zhí)行OkHttp
Rretrofit
是怎么完成線程切換和數(shù)據(jù)適配的
Retrofit
在網(wǎng)絡(luò)請求完成后所做的就只有兩件事,自動線程切換和數(shù)據(jù)適配;那么它是如何完成這些操作的呢?
關(guān)于數(shù)據(jù)適配
其實(shí)這個在上文注解解析問題中已經(jīng)回答了一部分了,這里我大概總結(jié)下流程,具體的數(shù)據(jù)解析適配過程細(xì)節(jié)需要大家私下去深入探討;在封裝的OkhttpCall
調(diào)用OKHttp
進(jìn)行網(wǎng)絡(luò)請求后會拿到接口響應(yīng)結(jié)果response
,這時候就要進(jìn)行數(shù)據(jù)格式的解析適配了,會調(diào)用parsePerson
方法,里面最終還是會調(diào)用Retrofit
中converterFactories
拿到數(shù)據(jù)轉(zhuǎn)換器工廠的集合其中的一個,所以當(dāng)我們創(chuàng)建Retrfoit
中進(jìn)行addConvetFactory
的時候,它保存在了Retrofit
當(dāng)中,交給了responseConverter.convert(responsebody)
,從而完成了數(shù)據(jù)轉(zhuǎn)換適配的過程
關(guān)于線程切換
首先我們要知道線程切換是發(fā)生在什么時候?毫無疑問,肯定是在最后一步,當(dāng)網(wǎng)絡(luò)請求回來后,且進(jìn)行數(shù)據(jù)解析后,那這樣我們向上尋根,發(fā)現(xiàn)最終數(shù)據(jù)解析由HttpServiceMethod
之后它會調(diào)用callAdapter.adapt()
進(jìn)行適配
protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call);
....
}
這意味著callAdapter
會去包裝OkhttpCall
,那么這個callAdapter
是來自哪里的,追本朔源,它其實(shí)在Retrofit
中的build
會去添加defaultCallAdapterFactories
,這個方法里就調(diào)用了DefaultCallAdapterFactory
,真正的線程切換就在這里
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
這個默認(rèn)的defaultCallAdapterFactories
會傳入平臺的defaultCallbackExecutor()
,由于我們平臺是Android
,所以它里面存放的就是主線程的Executor
,它里面就是一個Handler
static final class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) {
handler.post(r);
}
}
到這來看下DefaultCallAdapterFactory
中的enqueue
(代理模式+裝飾模式), 這里面使用了一個代理類delegate,它其實(shí)就是Retrofit
中的OkhttpCall
,最終請求結(jié)果完成后使用callbackExecutor.execute()
將線程變?yōu)橹骶€程,最終又回到了MainThreadExecutor
當(dāng)中
callbackExecutor.execute(
() -> {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on
// cancellation.
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
});
所以總的來說,這其實(shí)是一個層層疊加的過程,Retrofit
的線程切換原理本質(zhì)上就是Handler
消息機(jī)制;到這關(guān)于數(shù)據(jù)適配和線程切換的回答就告一段落了,有很多細(xì)節(jié)的東西沒有提到,有時間的話,需要自己去補(bǔ)充,用一張草圖來展示下Retrofit
對它們進(jìn)行的封裝
Android 學(xué)習(xí)筆錄
Android 性能優(yōu)化篇:https://qr18.cn/FVlo89
Android 車載篇:https://qr18.cn/F05ZCM
Android 逆向安全學(xué)習(xí)筆記:https://qr18.cn/CQ5TcL
Android Framework底層原理篇:https://qr18.cn/AQpN4J
Android 音視頻篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(內(nèi)含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源碼解析筆記:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知識體:https://qr18.cn/CyxarU
Android 核心筆記:https://qr21.cn/CaZQLo
Android 往年面試題錦:https://qr18.cn/CKV8OZ
2023年最新Android 面試題集https://qr18.cn/CgxrRy
Android 車載開發(fā)崗位面試習(xí)題:https://qr18.cn/FTlyCJ
音視頻面試題錦:https://qr18.cn/AcV6Ap