Retrofit是如何支援協程的

語言: CN / TW / HK

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