英文:
How does the following code work on Android
问题
在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
}
这里是他代码的链接:https://github.com/philipplackner/RetrofitCrashCourse/blob/master/app/src/main/java/com/plcoding/retrofitcrashcourse/MainActivity.kt
lifecycleScope
默认与主线程相关联,这意味着除非我们更改范围,上述代码中的Retrofit网络调用将在主线程上运行。
这个陈述是正确的,因为lifecycleScope
默认与主线程相关联,如果没有显式更改范围,Retrofit网络调用将在主线程上运行。
英文:
In one of Philip Lackner's tutorial, he is making the network call and UI changes like following:
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
}
Here is the pointer to his code: https://github.com/philipplackner/RetrofitCrashCourse/blob/master/app/src/main/java/com/plcoding/retrofitcrashcourse/MainActivity.kt
lifecycleScope is by default tied to main thread which means unless we change the scope, the retrofit network call in the above code is going to be run on main thread.
Is this statement correct, if not why?
答案1
得分: 1
不需要翻译的部分: It's a bit complicated, but I'll try to explain it based on what I've found out during my spelunking in the Retrofit code.
Yes, you are indeed running the call RetrofitInstance.api.getTodos() on the main dispatcher (implicitly via lifecycleScope), but...
No, you aren't running it on the main thread. Under the hood, suspend functions in Retrofit are implemented by using a combination of a suspendCancellableCoroutine and enqueue(...) as seen in the source code itself:
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)
}
})
}
}
With that in mind, enqueue(...) will eventually (somewhere down the rabbit hole) do the actual network request on another executor, and thus it doesn't cause any NetworkOnMainThread violations when you call it from the main dispatcher. Why? Well, it doesn't actually do any network calls on the main thread, per se.
Hope that clarifies it a bit.
需要翻译的部分:
是的,你确实在主线程上运行了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
违规。为什么?嗯,它实际上并没有在主线程上执行任何网络调用。
希望这能有点澄清。
英文:
It's a bit complicated, but I'll try to explain it based on what I've found out during my spelunking in the Retrofit code.
Yes, you are indeed running the call RetrofitInstance.api.getTodos()
on the main dispatcher (implicitly via lifecycleScope
), but...
No, you aren't running it on the main thread. Under the hood, suspend functions in Retrofit are implemented by using a combination of a suspendCancellableCoroutine
and enqueue(...)
as seen in the source code itself:
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)
}
})
}
}
With that in mind, enqueue(...)
will eventually (somewhere down the rabbit hole) do the actual network request on a another executor, and thus it's doesn't cause any NetworkOnMainThread
violations when you call it from the main dispatcher. Why? Well, it doesn't actually do any network calls on the main thread, per se.
Hope that clarifies it a bit.
答案2
得分: 0
不需要担心调用挂起函数时所在的线程或分发器,因为正确的挂起函数不会阻塞。如果需要特定的分发器,它会在内部使用 withContext
进行委托。在 Retrofit 的情况下,它根本不使用分发器,而是使用 Retrofit 自己的线程池,并使用 suspendCancellableCoroutine
进行挂起。
任何由熟练的 Kotlin 用户设计的库都会遵循相同的惯例。挂起函数永远不会阻塞。
英文:
Short explanation: You never have to worry about what thread or dispatcher you’re on when calling a suspend function, because a proper suspend function doesn’t block. If it needs a specific dispatcher, it internally delegates to it using withContext
. In the case of Retrofit, it doesn’t use a dispatcher at all—it uses Retrofit’s own thread pool and suspends using suspendCancellableCoroutine
.
Any library designed by competent Kotlin users will have suspend functions that follow this same convention. Suspend functions never block.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论