(眾所周知)Retrofit從2.6.0開始支持協(xié)程妹蔽。
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
使用
1.定義retrofit沥匈。
val retrofit: Retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(OkHttpClient.Builder().build())
.baseUrl("")
.build()
2.定義請求接口方法聪黎。
//示例接口
@GET("api/test")
suspend fun testApi(): MyResponse<TestBean>
3.然后在協(xié)程作用域中調(diào)用testApi()方法罕容,可以成功獲取結(jié)果。但是稿饰,如果testApi()方法沒有使用suspend關(guān)鍵字修飾锦秒,則會報錯:
Unable to create call adapter for com.package.name.net.MyResponse<TestBean>
for method ServiceApi.testApi
4.從錯誤信息可以知道,請求是成功發(fā)起并返回了的喉镰,只是在轉(zhuǎn)換結(jié)果對象的時候報錯了旅择。由此可以提出問題。
一 . 提出問題
為什么suspend關(guān)鍵字會影響返回結(jié)果bean的轉(zhuǎn)換侣姆?既是:Retrofit是如何支持Kotlin協(xié)程的生真?
二 . 源碼追溯
1.全局搜索“Unable to create call adapter for”,找到:
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
try {
//noinspection unchecked
return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(method, e, "Unable to create call adapter for %s", returnType);
}
}
再追溯“createCallAdapter”找到:
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
......
if (isKotlinSuspendFunction) {
......
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
//從這里報的錯誤
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
......
if (!isKotlinSuspendFunction) {
//如果不是suspend函數(shù)捺宗,則返回類型需要是retrofit2.Call<T>柱蟀。
//代碼走到這里就結(jié)束了。retrofit2并沒有為我們發(fā)起網(wǎng)絡(luò)請求蚜厉。
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}else if (continuationWantsResponse) {
//是suspend函數(shù)的處理长已,retrofit2執(zhí)行了網(wǎng)絡(luò)請求并返回了結(jié)果
//返回完整的response
return (HttpServiceMethod<ResponseT, ReturnT>)
new SuspendForResponse<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//是suspend函數(shù)的處理,retrofit2執(zhí)行了網(wǎng)絡(luò)請求并返回了結(jié)果
//返回response里面的body部分
......
}
}
1.判斷是否為suspend函數(shù)弯囊;
2.檢查不同情況下痰哨,參數(shù)類型和返回類型是否正確;
3.retrun 結(jié)果匾嘱;
判斷是否為掛起函數(shù):
private @Nullable ParameterHandler<?> parseParameter(
int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
......
//獲取接口方法參數(shù)最頂層的type類型斤斧。
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true;
return null;
}
......
}
這里的判斷依據(jù),跟suspend關(guān)鍵字有關(guān)霎烙。
使用studio自帶的kotlin工具反編譯查看java代碼可以發(fā)現(xiàn):
@GET("api/test")
@Nullable
Object testApi(@Query("test") @test Integer var1, @NotNull Continuation var2);
用suspend關(guān)鍵字修飾的方法撬讽,在參數(shù)里面自動添加了Continuation參數(shù)。
所以判斷方法是否有一個Continuation參數(shù)就可以判斷是否用suspend修飾了悬垃。
Continuation參數(shù)可以理解為一個回調(diào)游昼。
retrofit2幫我們發(fā)起網(wǎng)絡(luò)請求:
static final class SuspendForResponse<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
SuspendForResponse(
RequestFactory requestFactory,
okhttp3.Call.Factory callFactory,
Converter<ResponseBody, ResponseT> responseConverter,
CallAdapter<ResponseT, Call<ResponseT>> callAdapter) {
super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
}
@Override
protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call);
//noinspection unchecked Checked by reflection inside RequestFactory.
Continuation<Response<ResponseT>> continuation =
(Continuation<Response<ResponseT>>) args[args.length - 1];
// See SuspendForBody for explanation about this try/catch.
try {
return KotlinExtensions.awaitResponse(call, continuation);
} catch (Exception e) {
return KotlinExtensions.suspendAndThrow(e, continuation);
}
}
}
//前面已經(jīng)return了SuspendForResponse的構(gòu)造方法。
//到這里尝蠕,源碼追溯到頭了烘豌。
//再看adapt方法,必然會被調(diào)用看彼,追溯adapt方法廊佩,找到一個唯一調(diào)用囚聚。
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
//追溯invoke方法,找到一個唯一調(diào)用标锄。
public <T> T create(final Class<T> service) {
validateServiceInterface(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);
}
args = args != null ? args : emptyArgs;
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
再追溯create方法顽铸,就到我們自己定義的代碼了。追溯“KotlinExtensions.awaitResponse”料皇,再看refrofit2是怎么幫我們發(fā)起網(wǎng)絡(luò)請求的:
suspend fun <T> Call<T>.awaitResponse(): Response<T> {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
cancel()
}
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
continuation.resume(response)
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
1.首先這是一個retrofit2.Call<T>谓松,返回retrofit2.Response<T>的suspend擴(kuò)展方法。
該方法開啟了一個可取消的協(xié)程掛起函數(shù) suspendCancellableCoroutine践剂。
該函數(shù)可以把異步回調(diào)的寫法鬼譬,封裝轉(zhuǎn)換為協(xié)程同步的寫法。
這個掛起函數(shù)是使用Java的方式配合try catch調(diào)用的舷手,既:
// See SuspendForBody for explanation about this try/catch.
try {
return KotlinExtensions.awaitResponse(call, continuation);
} catch (Exception e) {
return KotlinExtensions.suspendAndThrow(e, continuation);
}
這樣就沒有報:Suspend function 'xxx' should be called only from a coroutine or another suspend function.的錯誤拧簸。
2.調(diào)用了enqueue方法,發(fā)起了一個異步請求男窟。這里的enqueue方法盆赤,就是對Okhttp的enqueue的封裝。
實(shí)際調(diào)用到的是:retrofit2.OkHttpCall<T>.enqueue(callBack)歉眷。
3.在得到結(jié)果后牺六,通過“continuation.resume(response)”或者“continuation.resumeWithException(t)”恢復(fù)程序繼續(xù)從這里執(zhí)行,并返回結(jié)果汗捡。
4.無論接口方法是否由suspend關(guān)鍵字修飾淑际,返回的結(jié)果,都經(jīng)過了轉(zhuǎn)換扇住。
用到了定義retrofit時傳入的GsonConverterFactory里面的GsonResponseBodyConverter春缕。
try {
T body = responseConverter.convert(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {......}
總結(jié)
1.retrofit2封裝了Okhttp,遠(yuǎn)程依賴只依賴retrofit就可以了艘蹋。
2.使用suspend關(guān)鍵字時锄贼,retrofit2框架內(nèi)部會幫我們判斷接口方法的合法合規(guī)性,然后在內(nèi)部開啟一個可取消的協(xié)程 suspendCancellableCoroutine{} 去執(zhí)行異步請求女阀。
實(shí)際上還是調(diào)用的Okhttp Request Call enqueue 那一套宅荤。
3.所以,api接口方法如果不用suspend關(guān)鍵字修飾浸策,則應(yīng)該寫成:
@GET("api/test")
fun testApi(@Query("test") test: Int?): retrofit2.Call<MyResponse<TestBean>>
//調(diào)用:
testService.testApi(0).enqueue(object :retrofit2.Callback<MdwlResponse<VersionBean>>{
override fun onResponse(
call: Call<MyResponse<TestBean>>,
response: Response<MyResponse<TestBean>>,
) { }
override fun onFailure(call: Call<MyResponse<TestBean>>, t: Throwable) { }
})
使用suspend關(guān)鍵字修飾冯键,則可以直接像同步方法一樣調(diào)用:
suspend fun xxx() = testService.testApi(0)
4.retrofit2是怎么實(shí)現(xiàn)切換線程的呢?下一篇繼續(xù)庸汗。