Retrofit可以不主动切换线程吗
我们知道安卓中需要在异步线程请求网络数据或进行繁重计算任务,在主线程更新UI,否则会崩溃。然而,最近做需求时我却发现自己忘了写切换线程,然而程序正常运行,所以看下源码,到底哪里默默帮我们完成了这一切。

Retrofit+Okhttp是我们常用的网络请求库,其定义API接口后可以通过动态代理来帮我们实际发出网络请求,我们能很快写出这样的代码:

fun initData() {
    viewModelScope.launch {
        val todoList = withContext(Dispatchers.IO) {
            todoApi.getTodoList()
        }.payload
        _todoListState.value = todoList ?: listOf()
    }
}

切换IO线程发出网络请求,切回主线程设置数据,回调式写法改为同步写法,非常舒服。但最近有个需求中,我发现我忘了写线程切换代码,然而没有报错:

fun initData() {
    viewModelScope.launch {
        val todoList = todoApi.getTodoList().payload
        _todoListState.value = todoList ?: listOf()
    }
}

为此,我们可以看一下他的实现,看内部哪里帮我们做了这个工作。

Retrofit实现简要分析

retrofit2.Retrofit#create

这里首先创建了个动态代理,API接口中定义的方法实际由loadServiceMethod(method).invoke(args)执行

Retrofit生成动态代理代码

(省略部分代码)

retrofit2.HttpServiceMethod#parseAnnotations

这个方法中完成了主要的操作,解析请求参数,构建Call对象

为suspend方法解析参数,准备请求代理类

可以看到,最终用一个SuspendForResponse/SuspendForBody封装了请求对象

SuspendForResponse

retrofit2.HttpServiceMethod.SuspendForResponse#adapt

awaitResponse

/home/zero/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/retrofit/2.9.0/409e80ce46c84ef7e74b6934032a73b8421eebe5/retrofit-2.9.0-sources.jar!/retrofit2/KotlinExtensions.kt:86

异步网络请求

retrofit2.Call#enqueue方法实际上是对OkHttp异步请求方法okhttp3.Call#enqueue的封装,总之他会在异步线程做网络请求并回调。

这里用到了一个叫suspendCancellableCoroutine的方法,而它在我们此前的协程专题分享中已经被提到了[Kotlin · Coroutines 在抖音] Suspend 揭秘 。这里是对Call对象回调式写法的封装,暂停(suspend)当前Continuation,直到网络请求返回再继续(resume)。


Q: 既然suspend了,它会阻塞当前线程吗?

**A:**注意,这里suspend翻译为“暂停”,它并不等同于线程的阻塞。Continuation经编译后实际实现为一个状态机,暂停(suspend)只是停在当前节点,线程可以继续处理其他Message,并没有阻塞。


结果返回后,continuation的Context没有变化,即Dispatcher没有改变,即切换回了原来的线程/线程池。所以,使用Kotlin Coroutines + Retrofit时可以不切换异步线程再发起网络请求。

Retrofit + RxJava写法可以不主动切换线程吗

直接看RxJava2CallAdapter即可。

Retrofit+RxJava

可以看到,这里根据设置CallAdapterFactory时传入的isAsync值不同,可能用同步请求方式(call.execute()),也可以用异步请求方式(call.enqueue(callback)),并没有其他切换线程操作。

即,若设置了isAsync = true,则会异步线程请求和回调,但你肯定还是需要切换回主线程来更新UI,免不了写observeOn

总结

若使用Kotlin Coroutines + Retrofit + OkHttp做网络请求,那么我们不用手动指定异步线程做网络请求,Retrofit已经处理了。

为何RxJava没有实现异步请求且自动切换回之前线程的特性,我认为可能是RxJava配合使用时不明确写observeOn,不能让使用者确信回调位置代码执行在某线程——RxJava流式写法,不写observeOn谁知道前面步骤切换到了哪个线程。再说拉到数据后也可能要进行一些仍然要在异步线程的其他处理,不一定就要切换回主线程,所以框架内就不多此一举了。


最后修改于 2023-03-30