以下代码在Android上是如何运行的?

huangapple go评论69阅读模式
英文:

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. 以下代码在Android上是如何运行的?

需要翻译的部分:
是的,你确实在主线程上运行了RetrofitInstance.api.getTodos()调用(隐式通过lifecycleScope),但是...
不,你并没有在主线程上运行它。在底层,Retrofit中的挂起函数是通过使用suspendCancellableCoroutineenqueue(...)的组合实现的,如源代码中所见

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违规。为什么?嗯,它实际上并没有在主线程上执行任何网络调用。
希望这能有点澄清。 以下代码在Android上是如何运行的?

英文:

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. 以下代码在Android上是如何运行的?

答案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.

huangapple
  • 本文由 发表于 2023年3月12日 06:34:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/75709986.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定