Retrofit是如何支援協程的
Kotlin的協程很好用,相信大家都用上了,也覺得很香,這不,Retrofit在最近的幾個版本中就支援了協程,更加方便我們處理網路請求。這裡不說協程的用法,我比較好奇Retrofit是怎麼識別並處理我們寫的 suspend 方法,下面就以retrofit:2.8.1的版本來看看究竟是如何實現的。
java眼中的suspend
在研究Retrofit程式碼之前,我們先看一個問題,kotlin完全相容java,但 suspend 是kotlin中的,java中並沒有,那這是怎麼相容的呢?為了看看究竟,我們可以把kotlin程式碼的位元組碼轉成java程式碼,通過Android Studio可以很方便檢視轉換的java程式碼。但這裡我們通過做一個小實驗來一看究竟:
定義一個kotlin類,裡面包含一個 suspend 方法:
class TestKt {
suspend fun foo(string: String): Int {
return 1
}
}
再定義一個java類,在裡面呼叫kotlin類的 suspend 方法:
可以看到在java類中呼叫suspend方法時,會需要多傳一個 Continuation 物件引數,這個引數在方法定義中並沒有申明,那應該就是kotlin轉換成java時自動新增的,並且該引數的泛型型別是 suspend 方法返回的型別。
再看一個實驗,定義Retrofit 的api介面,介面方法用 suspend 宣告:
interface INetApi {
@GET("")
suspend fun getBaidu(@Url url: String = "http://www.baidu.com"): Response<String>
}
定義java類實現該介面:
public class TestJava implements INetApi {
@Nullable
@Override
public Object getBaidu(@NotNull String url, @NotNull Continuation<? super Response<String>> $completion) {
return null;
}
}
在實現介面的 suspend 方法時,方法同樣會多了個 Continuation 引數,泛型型別也是 suspend 方法的返回型別。同時方法的 suspend 修飾沒有了,方法返回值變成了Object 。
通過上面的實驗,我們可以得出結論,kotlin中的 suspend 方法,在java眼裡,和普通的方法沒有區別,只是在方法的引數上,最後一個總是 Continuation 型別的引數。
Continuation 是什麼呢,它是kotlin協程中的介面,用於恢復掛起點。
Retrofit如何判斷suspend方法
Retrofit.java 的 create 方法是一切的起點,從這裡開始看:
對api介面的呼叫,都會走到代理方法中,紅框裡面的程式碼是真正的邏輯,上面兩個if判斷,一個判斷是Object而不是interface,第二個if判斷是不是介面的預設方法。順著紅框的程式碼繼續往下看 loadServiceMethod 方法:
裡面呼叫了 ServiceMethod.parseAnnotations 方法,並把結果快取了起來。往下看 ServiceMethod :
parseAnnotations 方法裡面有兩處關鍵地方,為圖中標記的1和2。先看第1處: RequestFactory.parseAnnotations
裡面呼叫了 build 方法,並且發現該類裡面有一個 isKotlinSuspendFunction 的變數,從名字可以看出,它用來標記是不是 suspend 方法。這就是我們要找的東西,激動萬分,直接看該變數在哪裡賦值的:
在 RequestFactory 裡面,Builder類的 parsePaeameter 方法中,如果result為null, allowContinuation 為true,且 parameterType 型別是 Continuation , isKotlinSuspendFunction 就賦值為true。
result 要為空,必須for迴圈裡面 parseParameterAnnotation 方法都返回空。
從 parseParameterAnnotation 方法最後一行註釋來看,當 annotations 裡面的註解都不屬於Retrofit定義的註解,就會返回null。
要搞明白這三個條件的意思,還得繼續看 parsePaeameter 在哪裡被呼叫。
好傢伙,正是在 build 方法中被呼叫:
從上下文程式碼可以看出,這裡的邏輯是解析api介面方法中的每個引數。 parameterAnnotationsArray 是引數上的註解, allowContinuation 表示是不是最後一個引數。
因此我們可以知道這三個條件的意思是:引數是方法的最後一個引數,型別是 Continuation ,且該引數上沒有屬於Retrofit的註解。
咦!是不是在哪見過…
前面我們做的小實驗中, suspend 方法變為java的程式碼後,最後一個引數不就滿足這三個條件嗎。
所以當api介面方法是 suspend 方法時, isKotlinSuspendFunction 變數為true。
處理suspend
識別出是 suspend 方法後,再來看是如何處理的。回到上面第2處關鍵程式碼,進入 HttpServiceMethod.parseAnnotations 方法:
這是該方法的上半部,如果是 suspend 方法,走到 if 程式碼塊裡面,通過 method.getGenericParameterTypes 獲取方法引數陣列,拿到最後一個引數,傳入 Utils.getParameterLowerBound 方法獲取到該引數宣告的泛型下界型別,為什麼是下界呢?
其實前面做實驗的時候也看到過, Continuation 的泛型是用super關鍵字,所以是下界。
這樣就獲取到了 Response<String> ,傳入 getRawType 方法會得到去掉泛型之後的型別,即Response。如果是Response,或繼續獲取Response的泛型型別,即 <String> 。
至此得到了 responseType ,再通過 Utils.ParameterizedTypeImpi 方法獲取 adapterType , adapterType 決定了使用哪個 CallAdapter ,注意這裡傳入了 Call.class 。
接著看 HttpServiceMethod.parseAnnotations 方法的下半部程式碼:
adapterType 被傳入 createCallAdapter 方法,由於上面獲取 adapterType 的時候傳入的是 Call.class ,所以獲取到的 CallAdapter 是 DefaultCallAdapterFactory 中返回的 CallAdapter 。
最後是根據 if 條件,返回不同型別的 HttpServiceMethod 。
HttpServiceMethod 的 invoke 方法:
在代理類中,最終會呼叫該方法,前面截圖看到過:
//Retrofit.create方法
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
該方法呼叫抽象方法 adapt ,具體的邏輯由上面三個不同型別的 HttpServiceMethod 實現。
如果不是 suspend 方法,就返回 CallAdapted :
CallAdapted 裡面的 adapt 方法直接呼叫了 callAdapter 的 adapt 方法,沒有增加什麼邏輯。
如果是 suspend 方法,且方法返回型別是 Response ,就返回 SuspendForResponse :
如果是 suspend 方法,方法返回型別不是 Response ,就返回 SuspendForResponse :
SuspendForResponse 與 SuspendForBody 邏輯差不多,呼叫了 KotlinExtensions 裡 await 相關的方法。
KotlinExtensions 是kotlin程式碼,現在從java程式碼又回到了kotlin程式碼中。
注意這裡呼叫 awit 時傳入了 Continuation 物件,前面做實驗時我們知道java呼叫 suspend 方法時,引數最後需要傳入 Continuation 物件。
Continuation 物件從kotlin傳到java的,現在又傳回kotlin。
await 裡面就是我們熟悉的協程程式碼了。 suspendCancellableCoroutine 和 suspendCoroutine 類似,會掛起協程,並在 resume 時恢復。
enqueue 方法執行的是 OkHttpCall 的enqueue,裡面使用okhttp發起網路請求,並通過 responseConverter 處理返回的資料。
網路請求完成後,成功就呼叫 resume 方法恢復掛起,並返回資料,失敗的話呼叫 resumeWithException 恢復掛起,並丟擲錯誤。
到此,就差不多理清了 Retrofit 處理協程的邏輯的。
關注我獲取更多知識或者投稿
原文連結: http://blog.csdn.net/ganduwei/article/details/118335325
- 【建議收藏】17個XML佈局小技巧
- Kotlin擴充套件方法進化之Context Receiver
- 網路請求元件封裝
- Flutter 繪製探索 | 箭頭端點的設計
- 在網頁上除錯手機上的APP
- Android應用安全解決方案
- Android應用安全開發之元件安全淺談
- Android 元件化架構設計從原理到實戰
- 涉作業系統技術領域,華為公開安卓應用程式遷移相關專利
- 避坑!!webview如何載入pdf ?
- Hook AMS APT實現集中式登入框架
- Retrofit是如何支援協程的
- 自定義雙向繫結框架-只需一個註解,簡單實用
- Android 10、11 儲存完全適配
- Java註解和註解解析器深耕,架構師必會
- 寫個更牛逼的Transform | Plugin 進階教程
- Android 騰訊 Matrix 原理分析(一):Matrix 概覽
- ART視角 | 如何自動回收native記憶體
- Android Gradle 多渠道打包
- Iterator 這些點你GET到了嗎?