查看原文
其他

当Retrofit 遇到协程,如何自动支持?

ganduwei 鸿洋 2021-10-12

本文作者


作者:ganduwei

链接:

https://blog.csdn.net/ganduwei/article/details/118335325

本文由作者授权发布。


1概述


Kotlin的协程很好用,相信大家都用上了,也觉得很香,这不,Retrofit在最近的几个版本中就支持了协程,更加方便我们处理网络请求。这里不说协程的用法,我比较好奇Retrofit是怎么识别并处理我们写的suspend方法,下面就以retrofit:2.8.1的版本来看看究竟是如何实现的。



2java眼中的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 = "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协程中的接口,用于恢复挂起点。


3Retrofit如何判断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类型是ContinuationisKotlinSuspendFunction就赋值为true。


result 要为空,必须for循环里面 parseParameterAnnotation 方法都返回空。



parseParameterAnnotation方法最后一行注释来看,当annotations里面的注解都不属于Retrofit定义的注解,就会返回null。


要搞明白这三个条件的意思,还得继续看parsePaeameter在哪里被调用。


好家伙,正是在build方法中被调用:



从上下文代码可以看出,这里的逻辑是解析api接口方法中的每个参数。parameterAnnotationsArray是参数上的注解,allowContinuation表示是不是最后一个参数。


因此我们可以知道这三个条件的意思是:参数是方法的最后一个参数,类型是Continuation,且该参数上没有属于Retrofit的注解。


咦!是不是在哪见过…


前面我们做的小实验中,suspend方法变为java的代码后,最后一个参数不就满足这三个条件吗。


所以当api接口方法是suspend方法时,isKotlinSuspendFunction变量为true。


4处理suspend


识别出是suspend方法后,再来看是如何处理的。回到上面第2处关键代码,进入HttpServiceMethod.parseAnnotations 方法:



这是该方法的上半部,如果是suspend方法,走到 if 代码块里面,通过method.getGenericParameterTypes获取方法参数数组,拿到最后一个参数,传入Utils.getParameterLowerBound方法获取到该参数声明的泛型下界类型,为什么是下界呢?



其实前面做实验的时候也看到过,Continuation的泛型是用super关键字,所以是下界。


这样就获取到了Response<String>,传入getRawType方法会得到去掉泛型之后的类型,即Response。如果是Response,或继续获取Response的泛型类型,即<String>


至此得到了responseType,再通过Utils.ParameterizedTypeImpi方法获取adapterTypeadapterType决定了使用哪个CallAdapter,注意这里传入了Call.class


接着看HttpServiceMethod.parseAnnotations 方法的下半部代码:



adapterType被传入createCallAdapter方法,由于上面获取adapterType的时候传入的是Call.class,所以获取到的CallAdapterDefaultCallAdapterFactory中返回的CallAdapter


最后是根据 if 条件,返回不同类型的HttpServiceMethod


HttpServiceMethod invoke方法:



在代理类中,最终会调用该方法,前面截图看到过:


//Retrofit.create方法

return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);


该方法调用抽象方法 adapt ,具体的逻辑由上面三个不同类型的HttpServiceMethod实现。


如果不是suspend方法,就返回CallAdapted



CallAdapted里面的adapt方法直接调用了callAdapteradapt方法,没有增加什么逻辑。


如果是suspend方法,且方法返回类型是Response,就返回SuspendForResponse



如果是suspend方法,方法返回类型不是Response,就返回SuspendForResponse



SuspendForResponseSuspendForBody逻辑差不多,调用了KotlinExtensionsawait相关的方法。


KotlinExtensions是kotlin代码,现在从java代码又回到了kotlin代码中。


注意这里调用 awit 时传入了Continuation对象,前面做实验时我们知道java调用suspend方法时,参数最后需要传入Continuation对象。


Continuation对象从kotlin传到java的,现在又传回kotlin。



await里面就是我们熟悉的协程代码了。suspendCancellableCoroutinesuspendCoroutine类似,会挂起协程,并在resume时恢复。


enqueue方法执行的是OkHttpCall的enqueue,里面使用okhttp发起网络请求,并通过responseConverter处理返回的数据。


网络请求完成后,成功就调用resume方法恢复挂起,并返回数据,失败的话调用resumeWithException恢复挂起,并抛出错误。


到此,就差不多理清了 Retrofit 处理协程的逻辑的。





最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!


推荐阅读


嗯?Android 消息机制还能难住我?告辞!
任性!我开发了一款自己用的天气预报app
关于 Bitmap 你要知道的一切


点击关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存