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类
可以看到在java类中调用suspend方法时,会需要多传一个Continuation对象参数,这个参数在方法定义中并没有申明,那应该就是kotlin转换成java时自动添加的,并且该参数的泛型类型是suspend方法返回的类型。

再看一个实验,定义Retrofit 的api接口,接口方法用suspend声明:

interface INetApi {
    
    
    @GET("")
    suspend fun getBaidu(@Url url: String = "https://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方法是一切的起点,从这里开始看:
Retrofit.java
对api接口的调用,都会走到代理方法中,红框里面的代码是真正的逻辑,上面两个if判断,一个判断是Object而不是interface,第二个if判断是不是接口的默认方法。顺着红框的代码继续往下看 loadServiceMethod 方法:
loadServiceMethod
里面调用了ServiceMethod.parseAnnotations方法,并把结果缓存了起来。往下看ServiceMethod:
ServiceMethodparseAnnotations方法里面有两处关键地方,为图中标记的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 处理协程的逻辑的。

猜你喜欢

转载自blog.csdn.net/ganduwei/article/details/118335325