最近迴歸看了一下Retrofit
的源碼,主要是由於項目接入了協程,因此想研究一下Retorift
是如何支持協程的。Retrofit
是在Version 2.6.0
開始支持協程的,因此本篇文章有關Retrofit
的源碼都是基於2.6.0
的。java
舒適提示,若是有
Retrofit
的源碼閱讀經驗,閱讀這篇文章將會輕鬆不少。
<!--放心你沒有進錯房間,這不是分析協程的文章,只是恰好談到協程,因此仍是簡單說下Retrofit
的實現。
不感興趣的能夠直接跳過下面的小插曲,放心不影響後續的閱讀。-->android
相信老鳥都應該很清楚,Retrofit
核心部分是create()
方法返回的動態代理(這裏就不詳細說明了,以後會有專門的文章分析動態代理)。就是下面這段代碼:git
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); private final Object[] emptyArgs = new Object[0]; @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } return loadServiceMethod(method).invoke(args != null ? args : emptyArgs); } }); }
第一眼看,跟我以前印象中的有點區別(也不知道是什麼版本),return
的時候竟然沒有adapt
方法了。開始還覺得有什麼重大的改變,其實也沒什麼,只是將以前的adapt
方法封裝到invoke
方法中。github
相關的method註解解析都放到ServiceMethod
中,有兩個關鍵函數調用,分別是RequestFactory
與HttpServiceMethod
的parseAnnotations()
方法。算法
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) { RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method); Type returnType = method.getGenericReturnType(); if (Utils.hasUnresolvableType(returnType)) { throw methodError(method, "Method return type must not include a type variable or wildcard: %s", returnType); } if (returnType == void.class) { throw methodError(method, "Service methods cannot return void."); } return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory); }
首先RequestFactory
中的parseAnnotations()
最終經過build()
方法來構建一個RequestFactory
,用來保存解析出來的方法信息。api
RequestFactory build() { //1.解析方法上的註解 for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } ... int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler<?>[parameterCount]; //2.循環遍歷方法中的各個參數,解析參數的註解 for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) { parameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter); } ... return new RequestFactory(this); }
能夠看到主要分爲兩步:微信
parseMethodAnnotation
來解析出請求的方式,例如GET
、POST
與PUT
等等;同時也會驗證一些註解的合規使用,例如Multipart
與FormUrlEncoded
只能使用一個。parseParameter
來解析出請求的參數信息,例如Path
、Url
與Query
等等;同時也對它們的合規使用作了驗證,例如QueryMap
與FieldMap
等註解它們的key
都必須爲String
類型。這些註解的解析都是在parseParameterAnnotation()
方法中進行的。上面的p == lastParameter
須要特別注意下,爲什麼要專門判斷該參數是否爲最後一個呢?請繼續向下看。架構
下面咱們來着重看下parseParameter
的源碼,由於從這裏開始就涉及到協程的判斷。異步
private @Nullable ParameterHandler<?> parseParameter( int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) { ParameterHandler<?> result = null; if (annotations != null) { for (Annotation annotation : annotations) { //1.解析方法參數的註解,並驗證它們的合法性 ParameterHandler<?> annotationAction = parseParameterAnnotation(p, parameterType, annotations, annotation); if (annotationAction == null) { continue; } //每一個參數都只能有一個註解 if (result != null) { throw parameterError(method, p, "Multiple Retrofit annotations found, only one allowed."); } result = annotationAction; } } //2.判斷是不是協程 if (result == null) { if (allowContinuation) { try { if (Utils.getRawType(parameterType) == Continuation.class) { isKotlinSuspendFunction = true; return null; } } catch (NoClassDefFoundError ignored) { } } throw parameterError(method, p, "No Retrofit annotation found."); } return result; }
第一點沒什麼好說的,裏面沒什麼邏輯,就是一個純註解解析與Converter
的選取。ide
第二點是關鍵點,用來判斷該方法的調用是否使用到了協程。同時有個allowContinuation
參數,這個是什麼呢?咱們向上看,發現它是方法中的一個參數,若是咱們繼續追溯就會發現它就是咱們以前特地須要注意的p == lastParameter
。
因此判斷是不是使用了協程有三步:
result
爲空,即該參數沒有註解allowContinuation
爲true
,便是最後一個參數Continuation.class
,說明該參數的類型爲Continuation
只有符合上述三點才能證實使用了協程,但腦海裏回想一下協程的寫法,發現徹底對不到這三點...
到這裏可能有的讀者已經開始蒙圈了,若是你沒有深刻了解協程的話,這個是正常的狀態。
別急,要理解這塊,還須要一點協程的原理知識,下面我來簡單說一下協程的部分實現原理。
咱們先來看下使用協程是怎麼寫的:
@GET("/v2/news") suspend fun newsGet(@QueryMap params: Map<String, String>): NewsResponse
這是一個標準的協程寫法,而後咱們再套用上面的條件,發現徹底匹配不到。
由於,這是不協程的原本面目。咱們思考一個問題,爲何使用協程要添加suspend
關鍵字呢?這是重點。你能夠多想幾分鐘。
(幾分鐘以後...)
不弔你們胃口了,我這裏就直接說結論。
由於在代碼編譯的過程當中會自動爲帶有suspend
的函數添加一個Continuation
類型的參數,並將其添加到最後面。因此上面的協程真正的面目是這樣的:
@GET("/v2/news") fun newsGet(@QueryMap params: Map<String, String>, c: Continuation<NewsResponse>): NewsResponse
如今咱們再來看上面的條件,發現可以所有符合了。
因爲篇幅有限,有關協程的原理實現就點到爲止,後續我會專門寫一個協程系列,但願到時可以讓讀者們認識到協程的真面目,你們能夠期待一下。
如今咱們已經知道了Retrofit
如何判斷一個方法是否使用了協程。那麼咱們再進入另外一個點:
Retrofit
如何將Call
直接轉化爲NewResonse
,簡單的說就是支持使newsGet
方法返回NewsResponse
。而這一步的轉化在HttpServiceMethod
中。
上面已經分析完RequestFactory
的parseAnnotations()
,如今再來看下HttpServiceMethod
中的parseAnnotations()
。
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) { boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction; boolean continuationWantsResponse = false; boolean continuationBodyNullable = false; Annotation[] annotations = method.getAnnotations(); Type adapterType; // 1. 是協程 if (isKotlinSuspendFunction) { Type[] parameterTypes = method.getGenericParameterTypes(); Type responseType = Utils.getParameterLowerBound(0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]); // 2. 判斷接口方法返回的類型是不是Response if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) { // Unwrap the actual body type from Response<T>. responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType); continuationWantsResponse = true; } else { // TODO figure out if type is nullable or not // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class) // Find the entry for method // Determine if return type is nullable or not } // 3. 注意:將方法返回類型假裝成Call類型,並將SkipCallbackExecutor註解添加到annotations中 adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType); annotations = SkipCallbackExecutorImpl.ensurePresent(annotations); } else { adapterType = method.getGenericReturnType(); } // 4. 建立CallAdapter,適配call,將其轉化成須要的類型 CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); Type responseType = callAdapter.responseType(); // 5. 建立Converter,將響應的數據轉化成對應的model類型 Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType); okhttp3.Call.Factory callFactory = retrofit.callFactory; // 6. 接口方法不是協程 if (!isKotlinSuspendFunction) { return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter); } else if (continuationWantsResponse) { // 7. 接口方法是協程,同時返回類型是Response類型 //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object. return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter); } else { // 8. 接口方法是協程,同時返回類型是body,即自定義的model類型 //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object. return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable); } }
代碼中已經解析的很清楚了,須要注意3,若是是協程會作兩步操做,首先將接口方法的返回類型假裝成Call
類型,而後再將SkipCallbackExecutor
手動添加到annotations
中。字面意思就在後續調用callAdapter.adapt(call)
時,跳過建立Executor
,簡單理解就是協程不須要Executor
來切換線程的。爲何這樣?這一點先放這裏,後續建立Call
的時候再說。
咱們直接看協程的7,8部分。7也不詳細分析,簡單提一下,它就是返回一個Response<T>
的類型,這個Retrofit
最基本的支持了。至於如何在使用協程時將Call<T>
轉化成Response<T>
原理與8基本相同,只是比8少一步,將它的body
轉化成對應的返回類型model
。因此下面咱們直接看8。
static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> { private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter; private final boolean isNullable; SuspendForBody(RequestFactory requestFactory, okhttp3.Call.Factory callFactory, Converter<ResponseBody, ResponseT> responseConverter, CallAdapter<ResponseT, Call<ResponseT>> callAdapter, boolean isNullable) { super(requestFactory, callFactory, responseConverter); this.callAdapter = callAdapter; this.isNullable = isNullable; } @Override protected Object adapt(Call<ResponseT> call, Object[] args) { // 1. 獲取適配的Call call = callAdapter.adapt(call); //noinspection unchecked Checked by reflection inside RequestFactory. // 2. 獲取協程的Continuation Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1]; return isNullable ? KotlinExtensions.awaitNullable(call, continuation) : KotlinExtensions.await(call, continuation); } }
咱們的關注點在adapt
,文章開頭已經說了,新版的Retrofit
將adapt
隱藏到invoke
中。而invoke
中調用的就是這個adapt
。
首先第一步,適配Call
,若是是RxJava
,這裏的callAdapter
就是RxJava2CallAdapter
,同時返回的就是Observable
,這個以前看過源碼的都知道。
但如今是協程,那麼這個時候的callAdapter
就是Retrofit
默認的DefaultCallAdapterFactory
@Override public @Nullable CallAdapter<?, ?> get( Type returnType, Annotation[] annotations, Retrofit retrofit) { // 1. 注意: 若是是協程,由於接口方法返回沒有使用Call,以前3的第一步假裝成Call的處理就在這裏體現了做用 if (getRawType(returnType) != Call.class) { return null; } if (!(returnType instanceof ParameterizedType)) { throw new IllegalArgumentException( "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>"); } final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType); // 2. 以前3的第二部就在這裏體現,因爲以前已經將SkipCallbackExecutor註解添加到annotations中,因此Executor直接爲null final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class) ? null : callbackExecutor; return new CallAdapter<Object, Call<?>>() { @Override public Type responseType() { return responseType; } @Override public Call<Object> adapt(Call<Object> call) { // 3. 最終調用adapt時候返回的就是它自己的Call,即不須要進行適配。 return executor == null ? call : new ExecutorCallbackCall<>(executor, call); } }; }
代碼註釋已經將以前3的做用解釋完了,咱們回到SuspendForBody
的adpt
,再看第二步。熟悉的一幕,又用到了最後的一個參數。這裏的isNullable
目前Retrofit
的版本都是false
,可能後續會支持空類型。但如今確定是不支持的,因此直接進入KotlinExtensions.await()
suspend fun <T : Any> Call<T>.await(): T { return suspendCancellableCoroutine { continuation -> continuation.invokeOnCancellation { cancel() } enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { if (response.isSuccessful) { // 1. 拿到body val body = response.body() if (body == null) { val invocation = call.request().tag(Invocation::class.java)!! val method = invocation.method() val e = KotlinNullPointerException("Response from " + method.declaringClass.name + '.' + method.name + " was null but response body type was declared as non-null") // 2. body爲空,喚起協程,拋出異常 continuation.resumeWithException(e) } else { // 3. 喚起協程,返回body continuation.resume(body) } } else { // 4. 喚起協程,拋出異常 continuation.resumeWithException(HttpException(response)) } } override fun onFailure(call: Call<T>, t: Throwable) { // 5. 喚起協程,拋出異常 continuation.resumeWithException(t) } }) } }
看到這段代碼,可能有的讀者很熟悉,已經明白了它的轉化。由於在Retrofit
以前的幾個版本,若是使用協程是不支持接口方法直接返回model
的,須要返回Call<T>
類型的數據。因此當時有的開源項目就是經過這個相似的extensions
方法來轉化成對應的model
。
遺憾的是,就是使用了Retrofit
的Version 2.6.0
以後的版本,我仍是看到有的人使用這一套來本身轉化,但願看到這篇文章的讀者不要再作重複的事情,將其交給Retrofit
自身來作就能夠了。
上面的extensions
做用就一個,經過suspendCancellableCoroutine
來建立一個協程,它是協程中幾個重要的建立協程的方法之一,這裏就不細說,後續開協程系列在詳細說明。
主要是onResponse
回調,協程經過掛起來執行耗時任務,而成功與失敗會分別經過resume()
與resumeWithExecption()
來喚起掛起的協程,讓它返回以前的掛起點,進行執行。而resumeWithExecption()
內部也是調用了resume()
,因此協程的喚起都是經過resume()
來操做的。調用resume()
以後,咱們能夠在調用協程的地方返回請求的結果。那麼一個完美的協程接口調用就是這樣實現的。
嗯,結束了,整理一下也不是很複雜吧。以後使用Retroift
寫協程時將通暢多了。
今天就這樣吧,協程部分就分析到這裏,Retrofit
的整個協程實現部分就分析結束了,我將關鍵點都特別進行了標註與說明,但願對分析Retrofit
的協程實現有所幫助。
最後,感謝你的閱讀,若是你有時間的話,推薦帶着這篇文章再去閱讀一下源碼,你將會有更深入的印象。
android_startup: 提供一種在應用啓動時可以更加簡單、高效的方式來初始化組件,優化啓動速度。不只支持Jetpack App Startup
的所有功能,還提供額外的同步與異步等待、線程控制與多進程支持等功能。
AwesomeGithub: 基於Github
客戶端,純練習項目,支持組件化開發,支持帳戶密碼與認證登錄。使用Kotlin
語言進行開發,項目架構是基於Jetpack&DataBinding
的MVVM
;項目中使用了Arouter
、Retrofit
、Coroutine
、Glide
、Dagger
與Hilt
等流行開源技術。
flutter_github: 基於Flutter
的跨平臺版本Github
客戶端,與AwesomeGithub
相對應。
android-api-analysis: 結合詳細的Demo
來全面解析Android
相關的知識點, 幫助讀者可以更快的掌握與理解所闡述的要點。
daily_algorithm: 每日一算法,由淺入深,歡迎加入一塊兒共勉。
微信搜索公衆號:【Android補給站】或者掃描下方二維碼進行關注