在 Philip Lackner 的教程之一中,他正在进行网络调用和 UI 更改,如下所示:
lifecycleScope.launchWhenCreated {
binding.progressBar.isVisible = true
val response = try {
RetrofitInstance.api.getTodos()
} catch(e: IOException) {
Log.e(TAG, "IOException, you might not have internet connection")
binding.progressBar.isVisible = false
return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response")
binding.progressBar.isVisible = false
return@launchWhenCreated
}
if(response.isSuccessful && response.body() != null) {
todoAdapter.todos = response.body()!!
} else {
Log.e(TAG, "Response not successful")
}
binding.progressBar.isVisible = false
}
lifecycleScope 默认绑定到主线程,这意味着除非我们更改范围,否则上面代码中的改造网络调用将在主线程上运行。
这个说法是否正确,如果不正确,为什么?
有点复杂,但我会尝试根据我在 Retrofit 代码中探索时发现的内容来解释它。
是的,你确实在主调度器上运行调用
RetrofitInstance.api.getTodos()
(隐含地通过lifecycleScope
),但是......
不,你没有在主线程上运行它。在幕后,Retrofit 中的挂起功能是通过使用
suspendCancellableCoroutine
和 enqueue(...)
的组合实现的,如源代码本身所示:
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)
}
})
}
}
考虑到这一点,
enqueue(...)
最终(在兔子洞的某个地方)在另一个执行者上执行实际的网络请求,因此当您从主调度程序调用它时,它不会导致任何NetworkOnMainThread
违规。为什么?好吧,它实际上并没有在主线程上进行任何网络调用,oer se.
希望能澄清一点。 :)
简短说明:在调用挂起函数时,你永远不必担心你在哪个线程或调度程序上,因为适当的挂起函数不会阻塞。如果它需要一个特定的调度程序,它会在内部使用
withContext
委托给它。在 Retrofit 的情况下,它根本不使用调度程序——它使用 Retrofit 自己的线程池并使用 suspendCancellableCoroutine
. 挂起
由有能力的 Kotlin 用户设计的任何库都将具有遵循相同约定的挂起函数。暂停功能永远不会阻塞。