android - 支持 Retrofit 挂起的线程切换优化

标签 android multithreading retrofit2 kotlin-coroutines coroutine

改造 2.6.0为我们带来了对 suspend 功能的支持。 Callenqueue 在后台使用:

Behind the scenes this behaves as if defined as fun user(...): Call and then invoked with Call.enqueue. You can also return Response for access to the response metadata.

这意味着请求是异步的,网络调用是在 ThreadPoolExecutor 表单 OkHttp 上完成的。我们不关心此 question 中描述的切换线程.

interface ApiService {
    @GET("custom/data/api")
    suspend fun getData(): String
}

class CustomViewModel: ViewMode() {
    // I omitted the initialization of the repository or usecase for simplicity
    fun getData() {
        viewModelScope.launch { // Dispatchers.Main
            apiService.getData() // network call on ThreadPoolExecutor
            // continue on Main thread
        }
    }
}

此时我们有一个线程上下文切换。

但是如果我想在网络调用后做一些额外的工作怎么办,例如映射。而且我不想在主线程上执行此操作:

fun getData() {
    viewModelScope.launch { // Dispatchers.Main
        val result = apiService.getData() // network call on ThreadPoolExecutor
        // continue on Main thread
        val uiData = withContext(Dispatchers.IO) { // Coroutine runs on a shared thread pool
            mapResult(result) // suspending long running task
        }
        // continue on Main thread
    }
}

此时我们有两个线程上下文切换:一个用于网络校准,另一个用于映射。

我的问题是关于优化的。不使用Retrofit接口(interface)中的suspend函数,而是使用一个线程切换协程调度器来进行网络调用等工作,是不是更优化?

interface ApiService {
    @GET("custom/data/api")
    fun getData(): Call<String>
}

fun getData() {
    viewModelScope.launch { // Dispatchers.Main
        // Main thread
        val uiData = withContext(Dispatchers.IO) { // Coroutine runs on a shared thread pool
            val result = apiService.getData().execute().body() // network call
            mapResult(result) // suspending long running task
        }
        // continue on Main thread
    }
}

我知道在一个简单的应用程序中优化不是那么大并且以纳秒为单位进行测量,但这不是主要问题。此外,问题不在于代码、异常处理等。问题在于理解多线程的内部机制,支持 Retrofit suspend 和协程。

最佳答案

如果协程在同一个线程中运行,则协程之间的上下文切换比线程之间的上下文切换要便宜得多。但是,如果它们在不同的线程中,则切换协程上下文需要进行昂贵的线程上下文切换。考虑下一个例子:

suspend fun doWork() {
    val data1 = fetchData1() // calling suspend function
    val data2 = fetchData2(data1) // calling another suspend function

    withContext(Dispatchers.Main) {
        updateUI(data2)
    }
} 

这里切换协程上下文需要昂贵的线程上下文切换。

在您的情况下,Api 调用发生在后台线程中,您使用 Dispatchers.IO 在后台线程中运行映射。所以映射有可能发生在同一个后台线程中。因此,您的操作并不昂贵。

但是如果 OkHttp 有它自己的 ExecutorService,确保协程和 OkHttp 可以使用相同的 Thread s 我们可以创建通用的 ExecutorService 并将其用于 OkHttp(在使用 OkHttpClient.Builder.dispatcher 构造 OkHttpClient 时设置它>) 和协程(在切换上下文时设置它 withContext(commomExecutorInstance.asCoroutineDispatcher()))。

关于android - 支持 Retrofit 挂起的线程切换优化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70413352/

相关文章:

以 Mongodb 作为后端的 Android App 和 Node.js (express framework)

android - 在 RxJava 中组合多个可观察对象

java - 将事件分派(dispatch)线程置于搁置状态,直到 Swing 计时器停止

java - Hibernate 源代码中的 volatile 屏障将为 "syncs state with other threads"。如何?

Java——套接字编程

android - Moshi改造错误: "Expected a string but was BEGIN_OBJECT"

android - 如何在 android 上使用 retrofit2 和 rxjava2 发出多个请求?

android - AndroidManifest.xml 的区别

android - Andengine中的旋转图像

Android Studio Gradle 错误 : Multiple dex files define